File: //usr/lib/ruby/site_ruby/1.8/puppet/pops/binder/binder.rb
# The Binder is responsible for processing layered bindings that can be used to setup an Injector.
#
# An instance should be created and a call to {#define_layers} should be made which will process the layered bindings
# (handle overrides, abstract entries etc.).
# The constructed hash with `key => InjectorEntry` mappings is obtained as {#injector_entries}, and is used to initialize an
# {Puppet::Pops::Binder::Injector Injector}.
#
# @api public
#
class Puppet::Pops::Binder::Binder
# @api private
attr_reader :injector_entries
# @api private
attr :id_index
# @api private
attr_reader :key_factory
# A parent Binder or nil
# @api private
attr_reader :parent
# The next anonymous key to use
# @api private
attr_reader :anonymous_key
# This binder's precedence
# @api private
attr_reader :binder_precedence
# @api public
def initialize(layered_bindings, parent_binder=nil)
@parent = parent_binder
@id_index = Hash.new() { |k, v| [] }
@key_factory = Puppet::Pops::Binder::KeyFactory.new()
# Resulting hash of all key -> binding
@injector_entries = {}
if @parent.nil?
@anonymous_key = 0
@binder_precedence = 0
else
# First anonymous key is the parent's next (non incremented key). (The parent can not change, it is
# the final, free key).
@anonymous_key = @parent.anonymous_key
@binder_precedence = @parent.binder_precedence + 1
end
define_layers(layered_bindings)
end
# Binds layers from highest to lowest as defined by the given LayeredBindings.
# @note
# The model should have been
# validated to get better error messages if the model is invalid. This implementation expects the model
# to be valid, and any errors raised will be more technical runtime errors.
#
# @param layered_bindings [Puppet::Pops::Binder::Bindings::LayeredBindings] the named and ordered layers
# @raise ArgumentError if this binder is already configured
# @raise ArgumentError if bindings with unresolved 'override' surfaces as an effective binding
# @raise ArgumentError if the given argument has the wrong type, or if model is invalid in some way
# @return [Puppet::Pops::Binder::Binder] self
# @api public
#
def define_layers(layered_bindings)
LayerProcessor.new(self, key_factory).bind(layered_bindings)
contribution_keys = []
# make one pass over entries to collect contributions, and check overrides
injector_entries.each do |k,v|
if key_factory.is_contributions_key?(k)
contribution_keys << [k,v]
elsif !v.is_resolved?()
raise ArgumentError, "Binding with unresolved 'override' detected: #{self.class.format_binding(v.binding)}}"
else
# if binding has an id, add it to the index
add_id_to_index(v.binding)
end
end
# If a lower level binder has contributions for a key also contributed to in this binder
# they must included in the higher shadowing contribution.
# If a contribution is made to an id that is defined in a parent
# contribute to an id that is defined in a lower binder, it must be promoted to this binder (copied) or
# there is risk of making the lower level injector dirty.
#
contribution_keys.each do |kv|
parent_contribution = lookup_in_parent(kv[0])
next unless parent_contribution
injector_entries[kv[0]] = kv[1] + parent_contributions
# key the multibind_id from the contribution key
multibind_id = key_factory.multibind_contribution_key_to_id(kv[0])
promote_matching_bindings(self, @parent, multibind_id)
end
end
private :define_layers
# @api private
def next_anonymous_key
tmp = @anonymous_key
@anonymous_key += 1
tmp
end
def add_id_to_index(binding)
return unless binding.is_a?(Puppet::Pops::Binder::Bindings::Multibinding) && !(id = binding.id).nil?
@id_index[id] = @id_index[id] << binding
end
def promote_matching_bindings(to_binder, from_binder, multibind_id)
return if from_binder.nil?
from_binder.id_index[ multibind_id ].each do |binding|
key = key_factory.binding_key(binding)
entry = lookup(key)
unless entry.precedence == @binder_precedence
# it is from a lower layer it must be promoted
injector_entries[ key ] = Puppet::Pops::Binder::InjectorEntry.new(binding, binder_precedence)
end
end
# recursive "up the parent chain" to promote all
promote_matching_bindings(to_binder, from_binder.parent, multibind_id)
end
def lookup_in_parent(key)
@parent.nil? ? nil : @parent.lookup(key)
end
def lookup(key)
if x = injector_entries[key]
return x
end
@parent ? @parent.lookup(key) : nil
end
# @api private
def self.format_binding(b)
type_name = Puppet::Pops::Types::TypeCalculator.new().string(b.type)
layer_name, bindings_name = get_named_binding_layer_and_name(b)
"binding: '#{type_name}/#{b.name}' in: '#{bindings_name}' in layer: '#{layer_name}'"
end
# @api private
def self.format_contribution_source(b)
layer_name, bindings_name = get_named_binding_layer_and_name(b)
"(layer: #{layer_name}, bindings: #{bindings_name})"
end
# @api private
def self.get_named_binding_layer_and_name(b)
return ['<unknown>', '<unknown>'] if b.nil?
return [get_named_layer(b), b.name] if b.is_a?(Puppet::Pops::Binder::Bindings::NamedBindings)
get_named_binding_layer_and_name(b.eContainer)
end
# @api private
def self.get_named_layer(b)
return '<unknown>' if b.nil?
return b.name if b.is_a?(Puppet::Pops::Binder::Bindings::NamedLayer)
get_named_layer(b.eContainer)
end
# Processes the information in a layer, aggregating it to the injector_entries hash in its parent binder.
# A LayerProcessor holds the intermediate state required while processing one layer.
#
# @api private
#
class LayerProcessor
attr :bindings
attr :binder
attr :key_factory
attr :contributions
attr :binder_precedence
def initialize(binder, key_factory)
@binder = binder
@binder_precedence = binder.binder_precedence
@key_factory = key_factory
@bindings = []
@contributions = []
@@bind_visitor ||= Puppet::Pops::Visitor.new(nil,"bind",0,0)
end
# Add the binding to the list of potentially effective bindings from this layer
# @api private
#
def add(b)
bindings << Puppet::Pops::Binder::InjectorEntry.new(b, binder_precedence)
end
# Add a multibind contribution
# @api private
#
def add_contribution(b)
contributions << Puppet::Pops::Binder::InjectorEntry.new(b, binder_precedence)
end
# Bind given abstract binding
# @api private
#
def bind(binding)
@@bind_visitor.visit_this(self, binding)
end
# @return [Puppet::Pops::Binder::InjectorEntry] the entry with the highest precedence
# @api private
def highest(b1, b2)
if b1.is_abstract? != b2.is_abstract?
# if one is abstract and the other is not, the non abstract wins
b1.is_abstract? ? b2 : b1
else
case b1.precedence <=> b2.precedence
when 1
b1
when -1
b2
when 0
raise_conflicting_binding(b1, b2)
end
end
end
# Raises a conflicting bindings error given two InjectorEntry's with same precedence in the same layer
# (if they are in different layers, something is seriously wrong)
def raise_conflicting_binding(b1, b2)
b1_layer_name, b1_bindings_name = binder.class.get_named_binding_layer_and_name(b1.binding)
b2_layer_name, b2_bindings_name = binder.class.get_named_binding_layer_and_name(b2.binding)
finality_msg = (b1.is_final? || b2.is_final?) ? ". Override of final binding not allowed" : ''
# TODO: Use of layer_name is not very good, it is not guaranteed to be unique
unless b1_layer_name == b2_layer_name
raise ArgumentError, [
'Conflicting binding for',
"'#{b1.binding.name}'",
'being resolved across layers',
"'#{b1_layer_name}' and",
"'#{b2_layer_name}'"
].join(' ')+finality_msg
end
# Conflicting bindings made from the same source
if b1_bindings_name == b2_bindings_name
raise ArgumentError, [
'Conflicting binding for name:',
"'#{b1.binding.name}'",
'in layer:',
"'#{b1_layer_name}', ",
'both from:',
"'#{b1_bindings_name}'"
].join(' ')+finality_msg
end
# Conflicting bindings from different sources
raise ArgumentError, [
'Conflicting binding for name:',
"'#{b1.binding.name}'",
'in layer:',
"'#{b1_layer_name}',",
'from:',
"'#{b1_bindings_name}', and",
"'#{b2_bindings_name}'"
].join(' ')+finality_msg
end
# Produces the key for the given Binding.
# @param binding [Puppet::Pops::Binder::Bindings::Binding] the binding to get a key for
# @return [Object] an opaque key
# @api private
#
def key(binding)
k = if is_contribution?(binding)
# contributions get a unique (sequential) key
binder.next_anonymous_key()
else
key_factory.binding_key(binding)
end
end
# @api private
def is_contribution?(binding)
! binding.multibind_id.nil?
end
# @api private
def bind_Binding(o)
if is_contribution?(o)
add_contribution(o)
else
add(o)
end
end
# @api private
def bind_Bindings(o)
o.bindings.each {|b| bind(b) }
end
# @api private
def bind_NamedBindings(o)
# Name is ignored here, it should be introspected when needed (in case of errors)
o.bindings.each {|b| bind(b) }
end
# Process layered bindings from highest to lowest layer
# @api private
#
def bind_LayeredBindings(o)
o.layers.each do |layer|
processor = LayerProcessor.new(binder, key_factory)
# All except abstract (==error) are transfered to injector_entries
processor.bind(layer).each do |k, v|
entry = binder.injector_entries[k]
unless key_factory.is_contributions_key?(k)
if v.is_abstract?()
layer_name, bindings_name = Puppet::Pops::Binder::Binder.get_named_binding_layer_and_name(v.binding)
type_name = key_factory.type_calculator.string(v.binding.type)
raise ArgumentError, "The abstract binding '#{type_name}/#{v.binding.name}' in '#{bindings_name}' in layer '#{layer_name}' was not overridden"
end
raise ArgumentError, "Internal Error - redefinition of key: #{k}, (should never happen)" if entry
binder.injector_entries[k] = v
else
entry ? entry << v : binder.injector_entries[k] = v
end
end
end
end
# Processes one named ("top level") layer consisting of a list of NamedBindings
# @api private
#
def bind_NamedLayer(o)
o.bindings.each {|b| bind(b) }
this_layer = {}
# process regular bindings
bindings.each do |b|
bkey = key(b.binding)
# ignore if a higher layer defined it (unless the lower is final), but ensure override gets resolved
# (override is not resolved across binders)
if x = binder.injector_entries[bkey]
if b.is_final?
raise_conflicting_binding(x, b)
end
x.mark_override_resolved()
next
end
# If a lower (parent) binder exposes a final binding it may not be overridden
#
if (x = binder.lookup_in_parent(bkey)) && x.is_final?
raise_conflicting_binding(x, b)
end
# if already found in this layer, one wins (and resolves override), or it is an error
existing = this_layer[bkey]
winner = existing ? highest(existing, b) : b
this_layer[bkey] = winner
if existing
winner.mark_override_resolved()
end
end
# Process contributions
# - organize map multibind_id to bindings with this id
# - for each id, create an array with the unique anonymous keys to the contributed bindings
# - bind the index to a special multibind contributions key (these are aggregated)
#
c_hash = Hash.new {|hash, key| hash[ key ] = [] }
contributions.each {|b| c_hash[ b.binding.multibind_id ] << b }
# - for each id
c_hash.each do |k, v|
index = v.collect do |b|
bkey = key(b.binding)
this_layer[bkey] = b
bkey
end
contributions_key = key_factory.multibind_contributions(k)
unless this_layer[contributions_key]
this_layer[contributions_key] = []
end
this_layer[contributions_key] += index
end
this_layer
end
end
end