class Orocos::RobyPlugin::DependencyInjection

Representation and manipulation of dependency injection selection

Attributes

defaults[R]
explicit[R]

Public Class Methods

new(*base) click to toggle source

creates a new DependencyInjection instance

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

# File lib/orocos/roby/instance_spec.rb, line 645
def initialize(*base)
    @explicit = Hash.new
    @defaults = Set.new
    if !base.empty?
        add(*base)
    end
end
resolve_default_selections(using_spec, default_selections) click to toggle source

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”

# File lib/orocos/roby/instance_spec.rb, line 973
def self.resolve_default_selections(using_spec, default_selections)
    if !default_selections || default_selections.empty?
        return using_spec
    end

    Engine.debug do
        Engine.debug "Resolving default selections"
        default_selections.map(&:to_s).sort.each do |sel|
            Engine.debug "    #{sel}"
        end
        Engine.debug "  into"
        using_spec.map { |k, v| [k.to_s, v.to_s] }.sort.each do |k, v|
            Engine.debug "    #{k} => #{v}"
        end
        Engine.debug "  rejections:"
        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|
            if using_spec[m]
                Engine.debug do
                    Engine.debug "  rejected #{selection.short_name}"
                    Engine.debug "    for #{m.short_name}"
                    Engine.debug "    reason: already explicitely selected"
                    break
                end
            elsif ambiguous_default_selections.has_key?(m)
                ambiguity = ambiguous_default_selections[m]
                Engine.debug do
                    Engine.debug "  rejected #{selection.short_name}"
                    Engine.debug "    for #{m.short_name}"
                    Engine.debug "    reason: ambiguity with"
                    ambiguity.each do |model|
                        Engine.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
                Engine.debug do
                    Engine.debug "  removing #{removed.short_name}"
                    Engine.debug "    for #{m.short_name}"
                    Engine.debug "    reason: ambiguity with"
                    Engine.debug "      #{selection.short_name}"
                    break
                end
            elsif selection != m
                Engine.debug do
                    Engine.debug "  adding #{selection.short_name}"
                    Engine.debug "    for #{m.short_name}"
                    break
                end
                resolved_default_selections[m] = selection
            end
        end
    end
    Engine.debug do
        Engine.debug "  selected defaults:"
        resolved_default_selections.each do |key, sel|
            Engine.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) click to toggle source

Helper method that resolves recursive selections in a dependency injection mapping

# File lib/orocos/roby/instance_spec.rb, line 950
def self.resolve_recursive_selection_mapping(spec)
    spec.map_value do |key, value|
        while (new_value = spec[value])
            value = new_value
        end
        value
    end
end
resolve_selection_recursively(value, spec) click to toggle source

Helper method that resolves one single object recursively

# File lib/orocos/roby/instance_spec.rb, line 960
def self.resolve_selection_recursively(value, spec)
    while (new_value = spec[value])
        value = new_value
    end
    value
end
validate_use_argument(*mappings) click to toggle source

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

@return <Hash, Array> the explicit selections and a list of

default selections
# File lib/orocos/roby/instance_spec.rb, line 1053
def self.validate_use_argument(*mappings)
    explicit = Hash.new
    defaults = Array.new
    mappings.each do |element|
        if element.kind_of?(Hash)
            explicit.merge!(element)
        else
            defaults << element
        end
    end
    return explicit, defaults
end

Public Instance Methods

==(obj) click to toggle source
# File lib/orocos/roby/instance_spec.rb, line 639
def ==(obj); eql?(obj) end
add(default0, default1, key0 → value0) click to toggle source
add([default0, default1], key0 → value0)
add(dependency_injection)

Add default and explicit selections in one call

# File lib/orocos/roby/instance_spec.rb, line 707
def add(*mappings)
    if mappings.size == 1 && mappings.first.kind_of?(DependencyInjection)
        deps = mappings.first
        explicit, defaults = deps.explicit, deps.defaults
    else
        explicit, defaults = DependencyInjection.validate_use_argument(*mappings)
    end

    add_explicit(explicit)
    add_defaults(defaults)
    self
end
add_defaults(list) click to toggle source

Add a list of objects to the default list.

# File lib/orocos/roby/instance_spec.rb, line 731
def add_defaults(list)
    # Invalidate the @resolved cached
    @resolved = nil
    @defaults |= list
    seen = Set.new
    @defaults.delete_if do |obj|
        if seen.include?(obj)
            true
        else
            seen << obj
            false
        end
    end
end
add_explicit(mappings) click to toggle source

Add a new dependency injection pattern to the current set

The new mapping overrides existing mappings

# File lib/orocos/roby/instance_spec.rb, line 723
def add_explicit(mappings)
    # Invalidate the @resolved cached
    @resolved = nil
    explicit.merge!(mappings)
    @explicit = DependencyInjection.resolve_recursive_selection_mapping(explicit)
end
candidates_for(*criteria) click to toggle source

Returns a list of candidates for the given selection criteria based on the information in self

# File lib/orocos/roby/instance_spec.rb, line 756
def candidates_for(*criteria)
    if defaults.empty?
        selection = self.explicit
    else
        @resolved ||= resolve
        return @resolved.candidates_for(*criteria)
    end

    criteria.each do |obj|
        required_models = []
        case obj
        when String
            if result = selection[obj]
                return [result]
            end
        when InstanceRequirements
            required_models = obj.models
        when DataServiceModel
            required_models = [obj]
        else
            if obj <= Component
                required_models = [obj]
            else
                raise ArgumentError, "unknown criteria object #{obj}, expected a string or an InstanceRequirements object"
            end
        end

        candidates = required_models.inject(Set.new) do |candidates, m|
            candidates << (selection[m] || selection[m.name])
        end
        candidates.delete(nil)
        if !candidates.empty?
            return candidates
        end
    end
    []
end
clear() click to toggle source
# File lib/orocos/roby/instance_spec.rb, line 658
def clear
    explicit.clear
    defaults.clear
end
each_selection_key(&block) click to toggle source
# File lib/orocos/roby/instance_spec.rb, line 925
def each_selection_key(&block)
    explicit.each_key(&block)
end
empty?() click to toggle source

True if this object contains no selection at all

# File lib/orocos/roby/instance_spec.rb, line 654
def empty?
    @explicit.empty? && @defaults.empty?
end
eql?(obj) click to toggle source
# File lib/orocos/roby/instance_spec.rb, line 634
def eql?(obj)
    obj.kind_of?(DependencyInjection) &&
        explicit == obj.explicit &&
        defaults == obj.defaults
end
has_selection?(*criteria) click to toggle source

Returns true if this object contains a selection for the given criteria

See #candidates_for for the description of the criteria list

# File lib/orocos/roby/instance_spec.rb, line 750
def has_selection?(*criteria)
    !candidates_for(*criteria).empty?
end
hash() click to toggle source
# File lib/orocos/roby/instance_spec.rb, line 633
def hash; [explicit, defaults].hash end
initialize_copy(from) click to toggle source
# File lib/orocos/roby/instance_spec.rb, line 805
def initialize_copy(from)
    @resolved = nil
    @explicit = from.explicit.map_value do |key, obj|
        case obj
        when InstanceRequirements, InstanceSelection
            obj.dup
        else obj
        end
    end
    @defaults = Set.new
    from.defaults.each do |obj|
        obj =
            case obj
            when InstanceRequirements, InstanceSelection
                obj.dup
            else obj
            end

        @defaults << obj
    end
end
map(&block) click to toggle source

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.

# File lib/orocos/roby/instance_spec.rb, line 896
def map(&block)
    copy = dup
    copy.map!(&block)
end
map!() { |v| ... } click to toggle source

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

# File lib/orocos/roby/instance_spec.rb, line 906
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) click to toggle source

Merge the selections in other into self.

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

# File lib/orocos/roby/instance_spec.rb, line 933
def merge(other)
    # Invalidate the @resolved cached
    @resolved = nil
    @explicit.merge!(other.explicit) do |match, model1, model2|
        if model1 <= model2
            model1
        elsif model2 <= model1
            model2
        else
            raise ArgumentError, "cannot use both #{model1} and #{model2} for #{match}"
        end
    end
    @defaults |= other.defaults
end
pretty_print(pp) click to toggle source
# File lib/orocos/roby/instance_spec.rb, line 669
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_unresolved() click to toggle source

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.

# File lib/orocos/roby/instance_spec.rb, line 881
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
resolve() click to toggle source

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

# File lib/orocos/roby/instance_spec.rb, line 829
def resolve
    result = DependencyInjection.resolve_default_selections(explicit, self.defaults)
    DependencyInjection.new(DependencyInjection.resolve_recursive_selection_mapping(result))
end
resolve_name(name, mappings) click to toggle source
# File lib/orocos/roby/instance_spec.rb, line 834
def resolve_name(name, mappings)
    if name =~ /(.*)\.(\w+)$/
        object_name, service_name = $1, $2
    else
        object_name = name
    end

    main_object = DependencyInjection.resolve_selection_recursively(object_name, mappings)
    if main_object.respond_to?(:to_str)
        raise NameResolutionError.new(object_name), "#{object_name} is not a known device or definition"
    end

    if service_name
        main_object = InstanceSelection.from_object(main_object, InstanceRequirements.new, false)
        if !(task_model = main_object.requirements.models.find { |m| m <= Roby::Task })
            raise ArgumentError, "while resolving #{name}: cannot explicitely select a service on something that is not a task"
        end

        if service = InstanceSelection.select_service_by_name(task_model, service_name)
            service
        else
            raise ArgumentError, "cannot find service #{service_name} on #{object_name}"
        end
    end

    main_object
end
resolve_names(mapping = self.explicit, &block) click to toggle source

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

# File lib/orocos/roby/instance_spec.rb, line 864
def resolve_names(mapping = self.explicit, &block)
    map! do |v|
        if v.respond_to?(:to_str)
            resolve_name(v, mapping)
        elsif v.respond_to?(:resolve_names)
            v.resolve_names(&block)
            v
        else v
        end
    end
end
selection_for(*criteria) click to toggle source

Like #candidates_for, but returns a single match

The match is either nil if there is an ambiguity (multiple matches) or if there is no match.

# File lib/orocos/roby/instance_spec.rb, line 798
def selection_for(*criteria)
    candidates = candidates_for(*criteria)
    if candidates.size == 1
        return candidates.first
    end
end