diff options
Diffstat (limited to 'lib/chef/node/immutable_collections.rb')
-rw-r--r-- | lib/chef/node/immutable_collections.rb | 161 |
1 files changed, 23 insertions, 138 deletions
diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb index 58ce58e61f..c12ee33268 100644 --- a/lib/chef/node/immutable_collections.rb +++ b/lib/chef/node/immutable_collections.rb @@ -30,16 +30,22 @@ class Chef e end - def convert_value(value, path = nil) + def convert_value(value) case value when Hash - ImmutableMash.new({}, __root__, __node__, __precedence__, path) + ImmutableMash.new(value, __root__, __node__, __precedence__) when Array - ImmutableArray.new([], __root__, __node__, __precedence__, path) + ImmutableArray.new(value, __root__, __node__, __precedence__) + when ImmutableMash, ImmutableArray + value else safe_dup(value).freeze end end + + def immutablize(value) + convert_value(value) + end end # == ImmutableArray @@ -53,50 +59,15 @@ class Chef # Chef::Node::Attribute's values, it overrides all reader methods to # detect staleness and raise an error if accessed when stale. class ImmutableArray < Array - alias_method :internal_clear, :clear - alias_method :internal_replace, :replace - alias_method :internal_push, :<< - alias_method :internal_to_a, :to_a - alias_method :internal_each, :each - private :internal_push, :internal_replace, :internal_clear, :internal_each - protected :internal_to_a - include Immutablize - methods = Array.instance_methods - Object.instance_methods + - [ :!, :!=, :<=>, :==, :===, :eql?, :to_s, :hash, :key, :has_key?, :inspect, :pretty_print, :pretty_print_inspect, :pretty_print_cycle, :pretty_print_instance_variables ] - - methods.each do |method| - define_method method do |*args, &block| - ensure_generated_cache! - super(*args, &block) - end - end - - def each - ensure_generated_cache! - # aggressively pre generate the cache, works around ruby being too smart and fiddling with internals - internal_each { |i| i.ensure_generated_cache! if i.respond_to?(:ensure_generated_cache!) } - super - end - - # because sometimes ruby gives us back Arrays or ImmutableArrays out of objects from things like #uniq or array slices - def return_normal_array(array) - if array.respond_to?(:internal_to_a, true) - array.internal_to_a - else - puts array.class - array.to_a - end - end - - def uniq - ensure_generated_cache! - return_normal_array(super) - end + alias :internal_push :<< + private :internal_push def initialize(array_data = []) - # Immutable collections no longer have initialized state + array_data.each do |value| + internal_push(immutablize(value)) + end end # For elements like Fixnums, true, nil... @@ -125,55 +96,8 @@ class Chef alias_method :to_array, :to_a - def [](*args) - ensure_generated_cache! - args.length > 1 ? return_normal_array(super) : super # correctly handle array slices - end - - def reset - @generated_cache = false - internal_clear # redundant? - end - - # @api private - def ensure_generated_cache! - generate_cache unless @generated_cache - @generated_cache = true - end - private - def combined_components(components) - combined_values = nil - components.each do |component| - values = __node__.attributes.instance_variable_get(component).read(*__path__) - next unless values.is_a?(Array) - combined_values ||= [] - combined_values += values - end - combined_values - end - - def get_array(component) - array = __node__.attributes.instance_variable_get(component).read(*__path__) - if array.is_a?(Array) - array - end # else nil - end - - def generate_cache - internal_clear - components = [] - components << combined_components(Attribute::DEFAULT_COMPONENTS) - components << get_array(:@normal) - components << combined_components(Attribute::OVERRIDE_COMPONENTS) - components << get_array(:@automatic) - highest = components.compact.last - if highest.is_a?(Array) - internal_replace( highest.each_with_index.map { |x, i| convert_value(x, __path__ + [ i ] ) } ) - end - end - # needed for __path__ def convert_key(key) key @@ -196,30 +120,19 @@ class Chef # it is stale. # * Values can be accessed in attr_reader-like fashion via method_missing. class ImmutableMash < Mash - alias_method :internal_clear, :clear - alias_method :internal_key?, :key? # FIXME: could bypass convert_key in Mash for perf - include Immutablize include CommonAPI - methods = Hash.instance_methods - Object.instance_methods + - [ :!, :!=, :<=>, :==, :===, :eql?, :to_s, :hash, :key, :has_key?, :inspect, :pretty_print, :pretty_print_inspect, :pretty_print_cycle, :pretty_print_instance_variables ] - - methods.each do |method| - define_method method do |*args, &block| - ensure_generated_cache! - super(*args, &block) - end - end - # this is for deep_merge usage, chef users must never touch this API # @api private def internal_set(key, value) - regular_writer(key, convert_value(value, __path__ + [ key ])) + regular_writer(key, convert_value(value)) end def initialize(mash_data = {}) - # Immutable collections no longer have initialized state + mash_data.each do |key, value| + internal_set(key, value) + end end alias :attribute? :has_key? @@ -255,39 +168,11 @@ class Chef alias_method :to_hash, :to_h - def [](key) - ensure_generated_cache! - super - end - - def reset - @generated_cache = false - internal_clear # redundant? - end - - # @api private - def ensure_generated_cache! - generate_cache unless @generated_cache - @generated_cache = true - end - - private - - def generate_cache - internal_clear - Attribute::COMPONENTS.reverse.each do |component| - subhash = __node__.attributes.instance_variable_get(component).read(*__path__) - unless subhash.nil? # FIXME: nil is used for not present - if subhash.kind_of?(Hash) - subhash.each_key do |key| - next if internal_key?(key) - internal_set(key, subhash[key]) - end - else - break - end - end - end + # For elements like Fixnums, true, nil... + def safe_dup(e) + e.dup + rescue TypeError + e end prepend Chef::Node::Mixin::StateTracking |