Representation and manipulation of dependency injection selection
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
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
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
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
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
# File lib/orocos/roby/instance_spec.rb, line 639 def ==(obj); eql?(obj) end
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 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 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
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
# File lib/orocos/roby/instance_spec.rb, line 658 def clear explicit.clear defaults.clear end
# File lib/orocos/roby/instance_spec.rb, line 925 def each_selection_key(&block) explicit.each_key(&block) end
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
# File lib/orocos/roby/instance_spec.rb, line 634 def eql?(obj) obj.kind_of?(DependencyInjection) && explicit == obj.explicit && defaults == obj.defaults end
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
# File lib/orocos/roby/instance_spec.rb, line 633 def hash; [explicit, defaults].hash end
# 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
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
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 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
# 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
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
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
# 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
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
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