Class: Syskit::DependencyInjection

Inherits:
Object
  • Object
show all
Extended by:
Logger::Forward, Logger::Hierarchy
Defined in:
lib/syskit/dependency_injection.rb

Overview

Representation and manipulation of dependency injection selection

Defined Under Namespace

Classes: SpecialDIValue

Constant Summary collapse

IGNORED_MODELS =
[DataService, TaskContext, Component, Composition,
Roby::Task, Roby::TaskService]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*base) ⇒ DependencyInjection

creates a new DependencyInjection instance

If arguments are provided, they must match the format expected by #add



22
23
24
25
26
27
28
# File 'lib/syskit/dependency_injection.rb', line 22

def initialize(*base)
    @explicit = Hash.new
    @defaults = Set.new
    if !base.empty?
        add(*base)
    end
end

Instance Attribute Details

#defaultsObject (readonly)

Returns the value of attribute defaults



8
9
10
# File 'lib/syskit/dependency_injection.rb', line 8

def defaults
  @defaults
end

#explicitObject (readonly)

Returns the value of attribute explicit



7
8
9
# File 'lib/syskit/dependency_injection.rb', line 7

def explicit
  @explicit
end

#resolvedObject (readonly)

Returns the value of attribute resolved



30
31
32
# File 'lib/syskit/dependency_injection.rb', line 30

def resolved
  @resolved
end

Class Method Details

.do_not_inheritObject



489
490
491
# File 'lib/syskit/dependency_injection.rb', line 489

def self.do_not_inherit
    @do_not_inherit ||= SpecialDIValue.new('do_not_inherit')
end

.find_name_resolution(name, mappings) ⇒ #instanciate?

Resolves a name into a component object

Parameters:

  • name (String)

    the name to be resolved. It can be a plain name, i.e. the name of a component in 'mappings', or a name.service, i.e. the name of a service for a component in 'mappings'

  • mappings (Hash)

    (see #explicit)

Returns:

  • (#instanciate, nil)

    the component model or nil if the name cannot be resolved

Raises:

  • (NameResolutionError)

    if the name cannot be resolved, either because the base name does not exist or the specified service cannot be found in it



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/syskit/dependency_injection.rb', line 374

def self.find_name_resolution(name, mappings)
    if name =~ /^(\w+)\.(.*)$/
        object_name, service_name = $1, $2
    else
        object_name = name
    end

    main_object = mappings[object_name]
    return if !main_object || main_object.respond_to?(:to_str)

    if service_name
        if !main_object.respond_to?(:find_data_service)
            raise NameResolutionError.new(object_name), "cannot select a service on #{main_object}"
        end
        if srv = main_object.find_data_service(service_name)
            return srv
        else
            raise NameResolutionError.new(object_name), "#{main_object} has no service called #{service_name}"
        end
    else return main_object
    end
end

.normalize_selected_object(value, key = nil) ⇒ Object



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/syskit/dependency_injection.rb', line 531

def self.normalize_selected_object(value, key = nil)
    if !value
        raise ArgumentError, "found nil as selection for #{key}, but it is not accepted anymore"
    end

    if value.kind_of?(InstanceSelection)
        value = value.component || value.selected
    end

    # 'value' must be one of String,Model<Component>,Component,DataService,Model<BoundDataService>,BoundDataService
    if !value.respond_to?(:to_str) &&
        !value.kind_of?(SpecialDIValue) &&
        !value.kind_of?(Component) &&
        !value.kind_of?(BoundDataService) &&
        !value.kind_of?(Models::BoundDataService) &&
        !value.kind_of?(Models::DataServiceModel) &&
        !value.kind_of?(InstanceRequirements) &&
        (!value.kind_of?(Class) || !(value <= Component))
        if value.respond_to?(:to_instance_requirements)
            value = value.to_instance_requirements
        else
            if key
                raise ArgumentError, "found #{value}(of class #{value.class}) as a selection for #{key}, but only name,component models,components,data service models and bound data services are allowed"
            else
                raise ArgumentError, "found #{value}(of class #{value.class}) as a selection, but only name,component models,components,data service models and bound data services are allowed"
            end
        end
    end
    value
end

.normalize_selection(selection) ⇒ Object

Normalizes an explicit selection

The input can map any of string, Component and DataService to string, Component, DataService and BoundDataService

A normalized selection has this form:

Raises:

  • ArgumentError if the key and value are not valid selection (see above)

  • ArgumentError if the selected component or service does not fullfill the key

  • AmbiguousServiceSelection if a component is selected for a data service, but there are multiple services of that type in the component



169
170
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
206
207
208
209
210
211
# File 'lib/syskit/dependency_injection.rb', line 169

def self.normalize_selection(selection)
    normalized = Hash.new
    selection.each do |key, value|
        # 'key' must be one of String, Component or DataService
        if !key.respond_to?(:to_str) &&
            !key.kind_of?(Models::DataServiceModel) &&
            (!key.kind_of?(Class) || !(key <= Component))

            raise ArgumentError, "found #{value} as a selection key, but only names, component models and data service models are allowed"
        end

        # 'value' must be one of String,Model<Component>,Component,DataService,Model<BoundDataService>,BoundDataService or nil
        value = normalize_selected_object(value, key)

        if key.respond_to?(:to_str)
            normalized[key] = value
            next
        end

        if value.respond_to?(:fullfills?)
            if !value.fullfills?(key)
                raise ArgumentError, "found #{value.short_name}(of class #{value.class}) as a selection for #{key.short_name}, but #{value.short_name} does not fullfill #{key.short_name}"
            end
        end

        if key <= Component
            if value.kind_of?(Models::BoundDataService)
                value = value.component_model
			elsif value.kind_of?(Syskit::BoundDataService)
                value = value.component
            end
            normalized[key] = value
        elsif key <= DataService
            if value.respond_to?(:find_data_service_from_type)
                value = value.find_data_service_from_type(key)
            end
            normalized[key] = value
        else
            raise NotImplementedError, "should not have get there, but did"
        end
    end
    normalized
end

.nothingObject



493
494
495
# File 'lib/syskit/dependency_injection.rb', line 493

def self.nothing
    @nothing ||= SpecialDIValue.new('nothing')
end

.partition_use_arguments(*mappings) ⇒ (Hash,Set)

Helper method that separates the default selections from the explicit selections in the call to #use

Returns:

  • ((Hash,Set))

    the explicit selections and a list of default selections



656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/syskit/dependency_injection.rb', line 656

def self.partition_use_arguments(*mappings)
    explicit = Hash.new
    defaults = Set.new
    mappings.each do |element|
        if element.kind_of?(Hash)
            explicit.merge!(element)
        else
            defaults << element
        end
    end
    return explicit, defaults
end

.resolve_default_selections(using_spec, default_selections) ⇒ Object

Helper methods that adds to a dependency inject mapping a list of default selections

Default selections are a list of objects for which no specification is used. They are resolved as “select X for all models of X for which there is no explicit selection already”



568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
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
# File 'lib/syskit/dependency_injection.rb', line 568

def self.resolve_default_selections(using_spec, default_selections)
    if !default_selections || default_selections.empty?
        return using_spec
    end

    debug do
        debug "Resolving default selections"
        default_selections.map(&:to_s).sort.each do |sel|
            debug "    #{sel}"
        end
        debug "  into"
        using_spec.map { |k, v| [k.to_s, v.to_s] }.sort.each do |k, v|
            debug "    #{k} => #{v}"
        end
        break
    end

    result = using_spec.dup

    ambiguous_default_selections = Hash.new
    resolved_default_selections = Hash.new

    default_selections.each do |selection|
        selection = resolve_selection_recursively(selection, using_spec)
        selection.each_fullfilled_model do |m|
            next if IGNORED_MODELS.include?(m)
            if selection.respond_to?(:find_all_data_services_from_type) &&
                m.kind_of?(Models::DataServiceModel)
                # Ignore if it is provided multiple times by the
                # selection

                next if selection.find_all_data_services_from_type(m).size != 1
            end

            if using_spec[m]
                debug do
                    debug "  rejected #{selection.short_name}"
                    debug "    for #{m.short_name}"
                    debug "    reason: already explicitely selected"
                    break
                end
            elsif ambiguous_default_selections.has_key?(m)
                ambiguity = ambiguous_default_selections[m]
                debug do
                    debug "  rejected #{selection.short_name}"
                    debug "    for #{m.short_name}"
                    debug "    reason: ambiguity with"
                    ambiguity.each do |model|
                        debug "      #{model.short_name}"
                    end
                    break
                end
                ambiguity << selection
            elsif resolved_default_selections[m] && resolved_default_selections[m] != selection
                removed = resolved_default_selections.delete(m)
                ambiguous_default_selections[m] = [selection, removed].to_set
                debug do
                    debug "  removing #{removed.short_name}"
                    debug "    for #{m.short_name}"
                    debug "    reason: ambiguity with"
                    debug "      #{selection.short_name}"
                    break
                end
            else
                debug do
                    debug "  adding #{selection.short_name}"
                    debug "    for #{m.short_name}"
                    break
                end
                resolved_default_selections[m] = selection
            end
        end
    end
    debug do
        debug "  selected defaults:"
        resolved_default_selections.each do |key, sel|
            debug "    #{key.respond_to?(:short_name) ? key.short_name : key}: #{sel}"
        end
        break
    end
    result.merge!(resolved_default_selections)
end

.resolve_recursive_selection_mapping(spec) ⇒ Object

Helper method that resolves recursive selections in a dependency injection mapping



499
500
501
502
503
# File 'lib/syskit/dependency_injection.rb', line 499

def self.resolve_recursive_selection_mapping(spec)
    spec.map_value do |key, value|
        resolve_selection_recursively(value, spec)
    end
end

.resolve_selection_recursively(value, spec) ⇒ Object

Helper method that resolves one single object recursively



506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/syskit/dependency_injection.rb', line 506

def self.resolve_selection_recursively(value, spec)
    while value && !value.respond_to?(:to_str)
        case value
        when Models::BoundDataService
            return value if !value.component_model.kind_of?(Class)
            component_model = value.component_model
            if (selected = spec[component_model]) && !selected.respond_to?(:to_str)
                if selected != component_model
                    new_value = selected.selected_for(value).selected_model
                end
            end
        when Module
            new_value = spec[value]
        else return value
        end
        return value if !new_value
        return value if value == new_value
        value = new_value
    end
    value
end

Instance Method Details

#==(obj) ⇒ Object



16
# File 'lib/syskit/dependency_injection.rb', line 16

def ==(obj); eql?(obj) end

#add(default0, default1, key0 = >value0) ⇒ Object #add([default0, default1], key0 = >value0) ⇒ Object #add(dependency_injection) ⇒ Object

Add default and explicit selections in one call



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/syskit/dependency_injection.rb', line 91

def add(*mappings)
    explicit, defaults = DependencyInjection.partition_use_arguments(*mappings)
    explicit = DependencyInjection.normalize_selection(explicit)

    filtered_defaults = Set.new
    defaults.each do |obj|
        if obj.kind_of?(DependencyInjection)
            # Do not use merge here. One wants to override the
            # existing selections with the new ones
            explicit = obj.explicit.merge!(explicit)
            filtered_defaults |= obj.defaults
        else
            filtered_defaults << DependencyInjection.normalize_selected_object(obj)
        end
    end

    add_explicit(explicit)
    add_defaults(filtered_defaults)
    self
end

#add_defaults(list) ⇒ Object

Add a list of objects to the default list.



214
215
216
217
218
219
# File 'lib/syskit/dependency_injection.rb', line 214

def add_defaults(list)
    # Invalidate the @resolved cached
    @resolved = nil
    list.each { |v| v.freeze if v.kind_of?(InstanceRequirements) }
    @defaults |= list
end

#add_explicit(mappings) ⇒ Object

Add a new dependency injection pattern to the current set

The new mapping overrides existing mappings



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/syskit/dependency_injection.rb', line 115

def add_explicit(mappings)
    # Invalidate the @resolved cached
    if !defaults.empty?
        @resolved = nil
    end
    mappings.each_value do |v|
        v.freeze if v.kind_of?(InstanceRequirements)
    end

    explicit.merge!(mappings) do |k, v1, v2|
        # There is a pathological case here. If v2 == k, then we
        # should keep the k => v1 mapping (because of recursive
        # resolution). However, this is the one case where it does
        # not work, as the merge! overrides the existing selection.
        #
        # However, when this happens, we can simply ignore the
        # identity selection
        if v2 == k then v1
        else v2
        end
    end

    @explicit = DependencyInjection.resolve_recursive_selection_mapping(explicit)
end

#add_mask(mask) ⇒ Object



140
141
142
143
144
# File 'lib/syskit/dependency_injection.rb', line 140

def add_mask(mask)
    mask.each do |key|
        explicit[key] = DependencyInjection.do_not_inherit
    end
end

#clearObject



43
44
45
46
# File 'lib/syskit/dependency_injection.rb', line 43

def clear
    explicit.clear
    defaults.clear
end

#direct_selection_for(obj) ⇒ Object



241
242
243
244
245
246
247
248
249
250
# File 'lib/syskit/dependency_injection.rb', line 241

def direct_selection_for(obj)
    if defaults.empty?
        if (sel = self.explicit[obj]) && sel != DependencyInjection.do_not_inherit
            sel
        end
    else
        @resolved ||= resolve
        return @resolved.direct_selection_for(obj)
    end
end

#each(&block) ⇒ Object

Enumerates the selected objects (not the keys)



472
473
474
475
476
477
# File 'lib/syskit/dependency_injection.rb', line 472

def each(&block)
    block_given? or return enum_for(__method__)
    explicit.each_value(&block)
    defaults.each(&block)
    self
end

#each_selection_key(&block) ⇒ Object



479
480
481
# File 'lib/syskit/dependency_injection.rb', line 479

def each_selection_key(&block)
    explicit.each_key(&block)
end

#empty?Boolean

True if this object contains no selection at all

Returns:

  • (Boolean)


39
40
41
# File 'lib/syskit/dependency_injection.rb', line 39

def empty?
    @explicit.empty? && @defaults.empty?
end

#eql?(obj) ⇒ Boolean

Returns:

  • (Boolean)


11
12
13
14
15
# File 'lib/syskit/dependency_injection.rb', line 11

def eql?(obj)
    obj.kind_of?(DependencyInjection) &&
        explicit == obj.explicit &&
        defaults == obj.defaults
end

#has_selection_for?(name) ⇒ Boolean

True if there is an explicit selection for the given name

Returns:

  • (Boolean)


147
148
149
# File 'lib/syskit/dependency_injection.rb', line 147

def has_selection_for?(name)
    !!direct_selection_for(name)
end

#hashObject



10
# File 'lib/syskit/dependency_injection.rb', line 10

def hash; [explicit, defaults].hash end

#initialize_copy(from) ⇒ Object



32
33
34
35
36
# File 'lib/syskit/dependency_injection.rb', line 32

def initialize_copy(from)
    super
    @explicit = from.explicit.dup
    @defaults = from.defaults.dup
end

#instance_selection_for(name, requirements) ⇒ InstanceSelection

Returns the non-ambiguous selection for the given name and requirements

Parameters:

  • name (String, nil)

    the name that should be used for resolution, or nil if there is no name

  • the (InstanceRequirements, nil)

    required models, or nil if none are specified

Returns:

Raises:

  • (IncompatibleComponentModels)

    if the various selections lead to component models that are incompatible (i.e. to two component models that are different and not subclassing one another)



230
231
232
233
234
235
236
237
238
239
# File 'lib/syskit/dependency_injection.rb', line 230

def instance_selection_for(name, requirements)
    instance, component_model, selected_services, used_keys =
        selection_for(name, requirements)
    selection = InstanceSelection.new(
        instance,
        InstanceRequirements.from_object(component_model, requirements),
        requirements,
        selected_services)
    return selection, used_keys
end

#map(&block) ⇒ Object

Create a new DependencyInjection object, with a modified selection

Like #map!, this method yields the [selection_key, selected_instance] pairs to a block that must return a new value for the for selected_instance.



442
443
444
445
# File 'lib/syskit/dependency_injection.rb', line 442

def map(&block)
    copy = dup
    copy.map!(&block)
end

#map!(&block) ⇒ Object

Changes the selections

This method yields the [selection_key, selected_instance] pairs to a block that must return a new value for the for selected_instance. It modifies self



452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/syskit/dependency_injection.rb', line 452

def map!(&block)
    # Invalidate the @resolved cached
    @resolved = nil
    changed = false
    explicit = self.explicit.map_value do |k, v|
        result = yield(v)
        changed ||= (result != v)
        result
    end
    if changed
        @explicit = DependencyInjection.resolve_recursive_selection_mapping(explicit)
    end

    @defaults.map! do |v|
        yield(v)
    end
    self
end

#merge(other) ⇒ Object

Merge the selections in other into self.

If both objects provide selections for the same keys, raises ArgumentError if the two selections are incompatible



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
# File 'lib/syskit/dependency_injection.rb', line 673

def merge(other)
    # Invalidate the @resolved cached
    @resolved = nil
    @explicit.merge!(other.explicit) do |match, model1, model2|
        if model1 == model2
            model1
        elsif model1.fullfills?(model2)
            model1
        elsif model2.fullfills?(model1)
            model2
        else
            raise ArgumentError, "cannot use both #{model1} and #{model2} for #{match}"
        end
    end
    @defaults |= other.defaults
end

#pretty_print(pp) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/syskit/dependency_injection.rb', line 54

def pretty_print(pp)
    pp.text "DependencyInjection"

    pp.breakable
    pp.text "Explicit:"
    if !explicit.empty?
        pp.nest(2) do
            pp.breakable
            explicit = self.explicit.map do |k, v|
                k = k.short_name
                v = v.short_name
                [k, "#{k} => #{v}"]
            end.sort_by(&:first)
            pp.seplist(explicit) do |kv|
                pp.text kv[1]
            end
        end
    end

    pp.breakable
    pp.text "Defaults:"
    if !defaults.empty?
        pp.nest(2) do
            pp.breakable
            defaults = self.defaults.map(&:to_s).sort
            pp.seplist(defaults) do |v|
                pp.text v.to_s
            end
        end
    end
end

#remove_unresolvedObject

Removes the unresolved instances from the list of selections

So far, unresolved selections are the ones that are represented as strings. The entries are not removed per se, but they are replaced by nil, to mark “do not use” selections.



427
428
429
430
431
432
433
434
435
# File 'lib/syskit/dependency_injection.rb', line 427

def remove_unresolved
    defaults.delete_if { |v| v.respond_to?(:to_str) }
    map! do |value|
        if value.respond_to?(:to_str)
            nil
        else value
        end
    end
end

#resolveObject

Resolves the selections by generating a direct mapping (as a hash) representing the required selection



356
357
358
359
360
# File 'lib/syskit/dependency_injection.rb', line 356

def resolve
    result = dup
    result.resolve!
    result
end

#resolve!Object



349
350
351
352
# File 'lib/syskit/dependency_injection.rb', line 349

def resolve!
    resolve_default_selections
    @explicit = DependencyInjection.resolve_recursive_selection_mapping(explicit)
end

#resolve_default_selectionsObject



344
345
346
347
# File 'lib/syskit/dependency_injection.rb', line 344

def resolve_default_selections
    @explicit = DependencyInjection.resolve_default_selections(explicit, defaults)
    defaults.clear
end

#resolve_names(mapping = Hash.new) ⇒ Set<String>

Recursively resolve the selections that are specified as strings using the provided block

Returns:

  • (Set<String>)

    the set of names that could not be resolved



401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/syskit/dependency_injection.rb', line 401

def resolve_names(mapping = Hash.new)
    unresolved = Set.new
    map! do |v|
        if v.respond_to?(:to_str)
            result = DependencyInjection.find_name_resolution(v, mapping)
            if !result
                unresolved << v
                v
            else result
            end

        elsif v.respond_to?(:resolve_names)
            # The value is e.g. an InstanceRequirements
            unresolved |= v.resolve_names(mapping)
            v
        else v
        end
    end
    unresolved
end

#selection_for(name, requirements) ⇒ Object

Returns the selected instance based on the given name and requirements

(Task,Model<Component>,Hash<Model<DataService>,Models::BoundDataService>,Set<Object>)

the selected instance.

If no matching selection is found, a matching model task proxy
is created. The first hash is a service mapping, from requested
service models to bound services in the task model. Finally, the
last set is the set of keys that have been used for the resolution.

Parameters:

  • name (String, nil)

    the selection name if there is one, or nil

  • requirements (InstanceRequirements)

    the requirements for the selected instance

Returns:

Raises:

  • (IncompatibleComponentModels)

    if the various selections lead to component models that are incompatible (i.e. to two component models that are different and not subclassing one another)



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/syskit/dependency_injection.rb', line 268

def selection_for(name, requirements)
    if defaults.empty?
        selection = self.explicit
    else
        @resolved ||= resolve
        return @resolved.selection_for(name, requirements)
    end

    used_keys = Set.new
    selected_services = Hash.new
    selections = Set.new
    if name && (sel = selection[name]) && (sel != DependencyInjection.do_not_inherit)
        used_keys << name
        if sel == DependencyInjection.nothing
            sel = requirements
        end
        selections << [sel]
        selection.each do |key, value|
            next if !value.respond_to?(:component_model) || value.component_model != sel

            requirements.each_required_model do |req_m|
                if key.respond_to?(:fullfills?) && key.fullfills?(req_m)
                    selected_services[req_m] ||= value
                end
            end
        end
    else
        requirements.each_required_model do |required_m|
            if sel = direct_selection_for(required_m)
                selections << [sel, required_m]
                used_keys << required_m
            else
                selections << [required_m, required_m]
            end
        end
    end

    selected_instance, selected_requirements = nil, InstanceRequirements.new
    requirements_name = nil
    selections.each do |sel_m, required_m|
        if sel_m.respond_to?(:to_task)
            sel_task = sel_m.to_task
            selected_instance ||= sel_task
            if selected_instance != sel_task
                raise ArgumentError, "task instances #{selected_instance} and #{sel_m} are both selected for #{required_m || requirements}, but they are not compatible"
            end
        end

        sel_m = sel_m.to_instance_requirements
        if sel_m.respond_to?(:name)
            requirements_name ||= sel_m.name
        end
        if sel_m.service
            if required_m
                selected_services[required_m] = sel_m.service
            else
                requirements.each_required_model do |req_m|
                    if sel_m.fullfills?(req_m)
                        selected_services[req_m] ||= sel_m.service
                    end
                end
            end
        end
        selected_requirements.merge(sel_m.to_component_model, keep_abstract: true)
    end
    if selections.size == 1
        selected_requirements.name = requirements_name
    end

    if selected_instance && !selected_instance.fullfills?(requirements, requirements.arguments)
        raise ArgumentError, "explicitly selected #{selected_instance}, but it does not fullfill the required #{requirements}"
    end

    return selected_instance, selected_requirements, selected_services, used_keys
end

#to_sObject

:nodoc:



48
49
50
51
52
# File 'lib/syskit/dependency_injection.rb', line 48

def to_s # :nodoc:
    explicit = self.explicit.map { |k, v| [k.to_s, v.to_s] }.sort_by(&:first).map { |k, v| "#{k} => #{v}" }
    defaults = self.defaults.map(&:to_s)
    "#{defaults.concat(explicit).join(", ")}"
end