File: //usr/lib/ruby/site_ruby/1.8/puppet/pops/binder/producers.rb
# This module contains the various producers used by Puppet Bindings.
# The main (abstract) class is {Puppet::Pops::Binder::Producers::Producer} which documents the
# Producer API and serves as a base class for all other producers.
# It is required that custom producers inherit from this producer (directly or indirectly).
#
# The selection of a Producer is typically performed by the Innjector when it configures itself
# from a Bindings model where a {Puppet::Pops::Binder::Bindings::ProducerDescriptor} describes
# which producer to use. The configuration uses this to create the concrete producer.
# It is possible to describe that a particular producer class is to be used, and also to describe that
# a custom producer (derived from Producer) should be used. This is available for both regular
# bindings as well as multi-bindings.
#
#
# @api public
#
module Puppet::Pops::Binder::Producers
# Producer is an abstract base class representing the base contract for a bound producer.
# Typically, when a lookup is performed it is the value that is returned (via a producer), but
# it is also possible to lookup the producer, and ask it to produce the value (the producer may
# return a series of values, which makes this especially useful).
#
# When looking up a producer, it is of importance to only use the API of the Producer class
# unless it is known that a particular custom producer class has been bound.
#
# Custom Producers
# ----------------
# The intent is that this class is derived for custom producers that require additional
# options/arguments when producing an instance. Such a custom producer may raise an error if called
# with too few arguments, or may implement specific `produce` methods and always raise an
# error on #produce indicating that this producer requires custom calls and that it can not
# be used as an implicit producer.
#
# Features of Producer
# --------------------
# The Producer class is abstract, but offers the ability to transform the produced result
# by passing the option `:transformer` which should be a Puppet Lambda Expression taking one argument
# and producing the transformed (wanted) result.
#
# @abstract
# @api public
#
class Producer
# A Puppet 3 AST Lambda Expression
# @api public
#
attr_reader :transformer
# Creates a Producer.
# Derived classes should call this constructor to get support for transformer lambda.
#
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @api public
#
def initialize(injector, binding, scope, options)
if transformer_lambda = options[:transformer]
if transformer_lambda.is_a?(Proc)
raise ArgumentError, "Transformer Proc must take one argument; value." unless transformer_lambda.arity == 1
@transformer = transformer_lambda
else
raise ArgumentError, "Transformer must be a LambdaExpression" unless transformer_lambda.is_a?(Puppet::Pops::Model::LambdaExpression)
raise ArgumentError, "Transformer lambda must take one argument; value." unless transformer_lambda.parameters.size() == 1
@transformer = Puppet::Pops::Parser::EvaluatingParser.new.closure(transformer_lambda, scope)
end
end
end
# Produces an instance.
# @param scope [Puppet::Parser:Scope] the scope to use for evaluation
# @param args [Object] arguments to custom producers, always empty for implicit productions
# @return [Object] the produced instance (should never be nil).
# @api public
#
def produce(scope, *args)
do_transformation(scope, internal_produce(scope))
end
# Returns the producer after possibly having recreated an internal/wrapped producer.
# This implementation returns `self`. A derived class may want to override this method
# to perform initialization/refresh of its internal state. This method is called when
# a producer is requested.
# @see Puppet::Pops::Binder::ProducerProducer for an example of implementation.
# @param scope [Puppet::Parser:Scope] the scope to use for evaluation
# @return [Puppet::Pops::Binder::Producer] the producer to use
# @api public
#
def producer(scope)
self
end
protected
# Derived classes should implement this method to do the production of a value
# @param scope [Puppet::Parser::Scope] the scope to use when performing lookup and evaluation
# @raise [NotImplementedError] this implementation always raises an error
# @abstract
# @api private
#
def internal_produce(scope)
raise NotImplementedError, "Producer-class '#{self.class.name}' should implement #internal_produce(scope)"
end
# Transforms the produced value if a transformer has been defined.
# @param scope [Puppet::Parser::Scope] the scope used for evaluation
# @param produced_value [Object, nil] the produced value (possibly nil)
# @return [Object] the transformed value if a transformer is defined, else the given `produced_value`
# @api private
#
def do_transformation(scope, produced_value)
return produced_value unless transformer
transformer.call(produced_value)
end
end
# Abstract Producer holding a value
# @abstract
# @api public
#
class AbstractValueProducer < Producer
# @api public
attr_reader :value
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Puppet::Pops::Model::LambdaExpression, nil] :value (nil) the value to produce
# @api public
#
def initialize(injector, binding, scope, options)
super
# nil is ok here, as an abstract value producer may be used to signal "not found"
@value = options[:value]
end
end
# Produces the same/singleton value on each production
# @api public
#
class SingletonProducer < AbstractValueProducer
protected
# @api private
def internal_produce(scope)
value()
end
end
# Produces a deep clone of its value on each production.
# @api public
#
class DeepCloningProducer < AbstractValueProducer
protected
# @api private
def internal_produce(scope)
case value
when Integer, Float, TrueClass, FalseClass, Symbol
# These are immutable
return value
when String
# ok if frozen, else fall through to default
return value() if value.frozen?
end
# The default: serialize/deserialize to get a deep copy
Marshal.load(Marshal.dump(value()))
end
end
# This abstract producer class remembers the injector and binding.
# @abstract
# @api public
#
class AbstractArgumentedProducer < Producer
# @api public
attr_reader :injector
# @api public
attr_reader :binding
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @api public
#
def initialize(injector, binding, scope, options)
super
@injector = injector
@binding = binding
end
end
# @api public
class InstantiatingProducer < AbstractArgumentedProducer
# @api public
attr_reader :the_class
# @api public
attr_reader :init_args
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [String] :class_name The name of the class to create instance of
# @option options [Array<Object>] :init_args ([]) Optional arguments to class constructor
# @api public
#
def initialize(injector, binding, scope, options)
# Better do this, even if a transformation of a created instance is kind of an odd thing to do, one can imagine
# sending it to a function for further detailing.
#
super
class_name = options[:class_name]
raise ArgumentError, "Option 'class_name' must be given for an InstantiatingProducer" unless class_name
# get class by name
@the_class = Puppet::Pops::Types::ClassLoader.provide(class_name)
@init_args = options[:init_args] || []
raise ArgumentError, "Can not load the class #{class_name} specified in binding named: '#{binding.name}'" unless @the_class
end
protected
# Performs initialization the same way as Assisted Inject does (but handle arguments to
# constructor)
# @api private
#
def internal_produce(scope)
result = nil
# A class :inject method wins over an instance :initialize if it is present, unless a more specific
# constructor exists. (i.e do not pick :inject from superclass if class has a constructor).
#
if the_class.respond_to?(:inject)
inject_method = the_class.method(:inject)
initialize_method = the_class.instance_method(:initialize)
if inject_method.owner <= initialize_method.owner
result = the_class.inject(injector, scope, binding, *init_args)
end
end
if result.nil?
result = the_class.new(*init_args)
end
result
end
end
# @api public
class FirstFoundProducer < Producer
# @api public
attr_reader :producers
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Array<Puppet::Pops::Binder::Producers::Producer>] :producers list of producers to consult. Required.
# @api public
#
def initialize(injector, binding, scope, options)
super
@producers = options[:producers]
raise ArgumentError, "Option :producers' must be set to a list of producers." if @producers.nil?
raise ArgumentError, "Given 'producers' option is not an Array" unless @producers.is_a?(Array)
end
protected
# @api private
def internal_produce(scope)
# return the first produced value that is non-nil (unfortunately there is no such enumerable method)
producers.reduce(nil) {|memo, p| break memo unless memo.nil?; p.produce(scope)}
end
end
# Evaluates a Puppet Expression and returns the result.
# This is typically used for strings with interpolated expressions.
# @api public
#
class EvaluatingProducer < Producer
# A Puppet 3 AST Expression
# @api public
#
attr_reader :expression
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Array<Puppet::Pops::Model::Expression>] :expression The expression to evaluate
# @api public
#
def initialize(injector, binding, scope, options)
super
@expression = options[:expression]
raise ArgumentError, "Option 'expression' must be given to an EvaluatingProducer." unless @expression
end
# @api private
def internal_produce(scope)
Puppet::Pops::Parser::EvaluatingParser.new.evaluate(scope, expression)
end
end
# @api public
class LookupProducer < AbstractArgumentedProducer
# @api public
attr_reader :type
# @api public
attr_reader :name
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binder [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Puppet::Pops::Types::PAnyType] :type The type to lookup
# @option options [String] :name ('') The name to lookup
# @api public
#
def initialize(injector, binder, scope, options)
super
@type = options[:type]
@name = options[:name] || ''
raise ArgumentError, "Option 'type' must be given in a LookupProducer." unless @type
end
protected
# @api private
def internal_produce(scope)
injector.lookup_type(scope, type, name)
end
end
# @api public
class LookupKeyProducer < LookupProducer
# @api public
attr_reader :key
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binder [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Puppet::Pops::Types::PAnyType] :type The type to lookup
# @option options [String] :name ('') The name to lookup
# @option options [Puppet::Pops::Types::PAnyType] :key The key to lookup in the hash
# @api public
#
def initialize(injector, binder, scope, options)
super
@key = options[:key]
raise ArgumentError, "Option 'key' must be given in a LookupKeyProducer." if key.nil?
end
protected
# @api private
def internal_produce(scope)
result = super
result.is_a?(Hash) ? result[key] : nil
end
end
# Produces the given producer, then uses that producer.
# @see ProducerProducer for the non singleton version
# @api public
#
class SingletonProducerProducer < Producer
# @api public
attr_reader :value_producer
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Puppet::Pops::Model::LambdaExpression] :producer_producer a producer of a value producer (required)
# @api public
#
def initialize(injector, binding, scope, options)
super
p = options[:producer_producer]
raise ArgumentError, "Option :producer_producer must be given in a SingletonProducerProducer" unless p
@value_producer = p.produce(scope)
end
protected
# @api private
def internal_produce(scope)
value_producer.produce(scope)
end
end
# A ProducerProducer creates a producer via another producer, and then uses this created producer
# to produce values. This is useful for custom production of series of values.
# On each request for a producer, this producer will reset its internal producer (i.e. restarting
# the series).
#
# @param producer_producer [#produce(scope)] the producer of the producer
#
# @api public
#
class ProducerProducer < Producer
# @api public
attr_reader :producer_producer
# @api public
attr_reader :value_producer
# Creates new ProducerProducer given a producer.
#
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Puppet::Pops::Binder::Producer] :producer_producer a producer of a value producer (required)
#
# @api public
#
def initialize(injector, binding, scope, options)
super
unless producer_producer = options[:producer_producer]
raise ArgumentError, "The option :producer_producer must be set in a ProducerProducer"
end
raise ArgumentError, "Argument must be a Producer" unless producer_producer.is_a?(Producer)
@producer_producer = producer_producer
@value_producer = nil
end
# Updates the internal state to use a new instance of the wrapped producer.
# @api public
#
def producer(scope)
@value_producer = @producer_producer.produce(scope)
self
end
protected
# Produces a value after having created an instance of the wrapped producer (if not already created).
# @api private
#
def internal_produce(scope, *args)
producer() unless value_producer
value_producer.produce(scope)
end
end
# This type of producer should only be created by the Injector.
#
# @api private
#
class AssistedInjectProducer < Producer
# An Assisted Inject Producer is created when a lookup is made of a type that is
# not bound. It does not support a transformer lambda.
# @note This initializer has a different signature than all others. Do not use in regular logic.
# @api private
#
def initialize(injector, clazz)
raise ArgumentError, "class must be given" unless clazz.is_a?(Class)
@injector = injector
@clazz = clazz
@inst = nil
end
def produce(scope, *args)
producer(scope, *args) unless @inst
@inst
end
# @api private
def producer(scope, *args)
@inst = nil
# A class :inject method wins over an instance :initialize if it is present, unless a more specific zero args
# constructor exists. (i.e do not pick :inject from superclass if class has a zero args constructor).
#
if @clazz.respond_to?(:inject)
inject_method = @clazz.method(:inject)
initialize_method = @clazz.instance_method(:initialize)
if inject_method.owner <= initialize_method.owner || initialize_method.arity != 0
@inst = @clazz.inject(@injector, scope, nil, *args)
end
end
if @inst.nil?
unless args.empty?
raise ArgumentError, "Assisted Inject can not pass arguments to no-args constructor when there is no class inject method."
end
@inst = @clazz.new()
end
self
end
end
# Abstract base class for multibind producers.
# Is suitable as base class for custom implementations of multibind producers.
# @abstract
# @api public
#
class MultibindProducer < AbstractArgumentedProducer
attr_reader :contributions_key
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
#
# @api public
#
def initialize(injector, binding, scope, options)
super
@contributions_key = injector.key_factory.multibind_contributions(binding.id)
end
# @param expected [Array<Puppet::Pops::Types::PAnyType>, Puppet::Pops::Types::PAnyType] expected type or types
# @param actual [Object, Puppet::Pops::Types::PAnyType> the actual value (or its type)
# @return [String] a formatted string for inclusion as detail in an error message
# @api private
#
def type_error_detail(expected, actual)
tc = injector.type_calculator
expected = [expected] unless expected.is_a?(Array)
actual_t = tc.is_ptype?(actual) ? actual : tc.infer(actual)
expstrs = expected.collect {|t| tc.string(t) }
"expected: #{expstrs.join(', or ')}, got: #{tc.string(actual_t)}"
end
end
# A configurable multibind producer for Array type multibindings.
#
# This implementation collects all contributions to the multibind and then combines them using the following rules:
#
# - all *unnamed* entries are added unless the option `:priority_on_unnamed` is set to true, in which case the unnamed
# contribution with the highest priority is added, and the rest are ignored (unless they have the same priority in which
# case an error is raised).
# - all *named* entries are handled the same way as *unnamed* but the option `:priority_on_named` controls their handling.
# - the option `:uniq` post processes the result to only contain unique entries
# - the option `:flatten` post processes the result by flattening all nested arrays.
# - If both `:flatten` and `:uniq` are true, flattening is done first.
#
# @note
# Collection accepts elements that comply with the array's element type, or the entire type (i.e. Array[element_type]).
# If the type is restrictive - e.g. Array[String] and an Array[String] is contributed, the result will not be type
# compliant without also using the `:flatten` option, and a type error will be raised. For an array with relaxed typing
# i.e. Array[Data], it is valid to produce a result such as `['a', ['b', 'c'], 'd']` and no flattening is required
# and no error is raised (but using the array needs to be aware of potential array, non-array entries.
# The use of the option `:flatten` controls how the result is flattened.
#
# @api public
#
class ArrayMultibindProducer < MultibindProducer
# @return [Boolean] whether the result should be made contain unique (non-equal) entries or not
# @api public
attr_reader :uniq
# @return [Boolean, Integer] If result should be flattened (true), or not (false), or flattened to given level (0 = none, -1 = all)
# @api public
attr_reader :flatten
# @return [Boolean] whether priority should be considered for named contributions
# @api public
attr_reader :priority_on_named
# @return [Boolean] whether priority should be considered for unnamed contributions
# @api public
attr_reader :priority_on_unnamed
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Boolean] :uniq (false) if collected result should be post-processed to contain only unique entries
# @option options [Boolean, Integer] :flatten (false) if collected result should be post-processed so all contained arrays
# are flattened. May be set to an Integer value to indicate the level of recursion (-1 is endless, 0 is none).
# @option options [Boolean] :priority_on_named (true) if highest precedented named element should win or if all should be included
# @option options [Boolean] :priority_on_unnamed (false) if highest precedented unnamed element should win or if all should be included
# @api public
#
def initialize(injector, binding, scope, options)
super
@uniq = !!options[:uniq]
@flatten = options[:flatten]
@priority_on_named = options[:priority_on_named].nil? ? true : options[:priority_on_name]
@priority_on_unnamed = !!options[:priority_on_unnamed]
case @flatten
when Integer
when true
@flatten = -1
when false
@flatten = nil
when NilClass
@flatten = nil
else
raise ArgumentError, "Option :flatten must be nil, Boolean, or an integer value" unless @flatten.is_a?(Integer)
end
end
protected
# @api private
def internal_produce(scope)
seen = {}
included_keys = []
injector.get_contributions(scope, contributions_key).each do |element|
key = element[0]
entry = element[1]
name = entry.binding.name
existing = seen[name]
empty_name = name.nil? || name.empty?
if existing
if empty_name && priority_on_unnamed
if (seen[name] <=> entry) >= 0
raise ArgumentError, "Duplicate key (same priority) contributed to Array Multibinding '#{binding.name}' with unnamed entry."
end
next
elsif !empty_name && priority_on_named
if (seen[name] <=> entry) >= 0
raise ArgumentError, "Duplicate key (same priority) contributed to Array Multibinding '#{binding.name}', key: '#{name}'."
end
next
end
else
seen[name] = entry
end
included_keys << key
end
result = included_keys.collect do |k|
x = injector.lookup_key(scope, k)
assert_type(binding(), injector.type_calculator(), x)
x
end
result.flatten!(flatten) if flatten
result.uniq! if uniq
result
end
# @api private
def assert_type(binding, tc, value)
infered = tc.infer(value)
unless tc.assignable?(binding.type.element_type, infered) || tc.assignable?(binding.type, infered)
raise ArgumentError, ["Type Error: contribution to '#{binding.name}' does not match type of multibind, ",
"#{type_error_detail([binding.type.element_type, binding.type], value)}"].join()
end
end
end
# @api public
class HashMultibindProducer < MultibindProducer
# @return [Symbol] One of `:error`, `:merge`, `:append`, `:priority`, `:ignore`
# @api public
attr_reader :conflict_resolution
# @return [Boolean]
# @api public
attr_reader :uniq
# @return [Boolean, Integer] Flatten all if true, or none if false, or to given level (0 = none, -1 = all)
# @api public
attr_reader :flatten
# The hash multibind producer provides options to control conflict resolution.
# By default, the hash is produced using `:priority` resolution - the highest entry is selected, the rest are
# ignored unless they have the same priority which is an error.
#
# @param injector [Puppet::Pops::Binder::Injector] The injector where the lookup originates
# @param binding [Puppet::Pops::Binder::Bindings::Binding, nil] The binding using this producer
# @param scope [Puppet::Parser::Scope] The scope to use for evaluation
# @option options [Puppet::Pops::Model::LambdaExpression] :transformer (nil) a transformer of produced value
# @option options [Symbol, String] :conflict_resolution (:priority) One of `:error`, `:merge`, `:append`, `:priority`, `:ignore`
# <ul><li> `ignore` the first found highest priority contribution is used, the rest are ignored</li>
# <li>`error` any duplicate key is an error</li>
# <li>`append` element type must be compatible with Array, makes elements be arrays and appends all found</li>
# <li>`merge` element type must be compatible with hash, merges hashes with retention of highest priority hash content</li>
# <li>`priority` the first found highest priority contribution is used, duplicates with same priority raises and error, the rest are
# ignored.</li></ul>
# @option options [Boolean, Integer] :flatten (false) If appended should be flattened. Also see {#flatten}.
# @option options [Boolean] :uniq (false) If appended result should be made unique.
#
# @api public
#
def initialize(injector, binding, scope, options)
super
@conflict_resolution = options[:conflict_resolution].nil? ? :priority : options[:conflict_resolution]
@uniq = !!options[:uniq]
@flatten = options[:flatten]
unless [:error, :merge, :append, :priority, :ignore].include?(@conflict_resolution)
raise ArgumentError, "Unknown conflict_resolution for Multibind Hash: '#{@conflict_resolution}."
end
case @flatten
when Integer
when true
@flatten = -1
when false
@flatten = nil
when NilClass
@flatten = nil
else
raise ArgumentError, "Option :flatten must be nil, Boolean, or an integer value" unless @flatten.is_a?(Integer)
end
if uniq || flatten || conflict_resolution.to_s == 'append'
etype = binding.type.element_type
unless etype.class == Puppet::Pops::Types::PDataType || etype.is_a?(Puppet::Pops::Types::PArrayType)
detail = []
detail << ":uniq" if uniq
detail << ":flatten" if flatten
detail << ":conflict_resolution => :append" if conflict_resolution.to_s == 'append'
raise ArgumentError, ["Options #{detail.join(', and ')} cannot be used with a Multibind ",
"of type #{injector.type_calculator.string(binding.type)}"].join()
end
end
end
protected
# @api private
def internal_produce(scope)
seen = {}
included_entries = []
injector.get_contributions(scope, contributions_key).each do |element|
key = element[0]
entry = element[1]
name = entry.binding.name
raise ArgumentError, "A Hash Multibind contribution to '#{binding.name}' must have a name." if name.nil? || name.empty?
existing = seen[name]
if existing
case conflict_resolution.to_s
when 'priority'
# skip if duplicate has lower prio
if (comparison = (seen[name] <=> entry)) <= 0
raise ArgumentError, "Internal Error: contributions not given in decreasing precedence order" unless comparison == 0
raise ArgumentError, "Duplicate key (same priority) contributed to Hash Multibinding '#{binding.name}', key: '#{name}'."
end
next
when 'ignore'
# skip, ignore conflict if prio is the same
next
when 'error'
raise ArgumentError, "Duplicate key contributed to Hash Multibinding '#{binding.name}', key: '#{name}'."
end
else
seen[name] = entry
end
included_entries << [key, entry]
end
result = {}
included_entries.each do |element|
k = element[ 0 ]
entry = element[ 1 ]
x = injector.lookup_key(scope, k)
name = entry.binding.name
assert_type(binding(), injector.type_calculator(), name, x)
if result[ name ]
merge(result, name, result[ name ], x)
else
result[ name ] = conflict_resolution().to_s == 'append' ? [x] : x
end
end
result
end
# @api private
def merge(result, name, higher, lower)
case conflict_resolution.to_s
when 'append'
unless higher.is_a?(Array)
higher = [higher]
end
tmp = higher + [lower]
tmp.flatten!(flatten) if flatten
tmp.uniq! if uniq
result[name] = tmp
when 'merge'
result[name] = lower.merge(higher)
end
end
# @api private
def assert_type(binding, tc, key, value)
unless tc.instance?(binding.type.key_type, key)
raise ArgumentError, ["Type Error: key contribution to #{binding.name}['#{key}'] ",
"is incompatible with key type: #{tc.label(binding.type)}, ",
type_error_detail(binding.type.key_type, key)].join()
end
if key.nil? || !key.is_a?(String) || key.empty?
raise ArgumentError, "Entry contributing to multibind hash with id '#{binding.id}' must have a name."
end
unless tc.instance?(binding.type.element_type, value)
raise ArgumentError, ["Type Error: value contribution to #{binding.name}['#{key}'] ",
"is incompatible, ",
type_error_detail(binding.type.element_type, value)].join()
end
end
end
end