lib/bindata/sanitize.rb in bindata-1.3.1 vs lib/bindata/sanitize.rb in bindata-1.4.0

- old
+ new

@@ -1,282 +1,268 @@ require 'bindata/registry' module BinData - # When a BinData object is instantiated, it can be supplied parameters to - # determine its behaviour. These parameters must be sanitized to ensure - # their values are valid. When instantiating many objects, such as an array - # of records, there is much duplicated validation. - # - # The purpose of the sanitizing code is to eliminate the duplicated - # validation. - # - # SanitizedParameters is a hash-like collection of parameters. Its purpose - # it to recursively sanitize the parameters of an entire BinData object chain - # at a single time. - class SanitizedParameters < Hash + # Subclasses of this are sanitized + class SanitizedParameter; end - def initialize(parameters, the_class) - parameters.each_pair { |key, value| self[key.to_sym] = value } + class SanitizedPrototype < SanitizedParameter + def initialize(obj_type, obj_params, endian) + endian = endian.endian if endian.respond_to? :endian + obj_params ||= {} - @all_sanitized = false - @the_class = the_class - ensure_no_nil_values + @obj_class = RegisteredClasses.lookup(obj_type, endian) + @obj_params = SanitizedParameters.new(obj_params, @obj_class, endian) end - alias_method :has_parameter?, :has_key? + def instantiate(value = nil, parent = nil) + @factory ||= @obj_class.new(@obj_params) - def needs_sanitizing?(key) - parameter = self[key] + @factory.new(value, parent) + end + end + #---------------------------------------------------------------------------- - parameter and not parameter.is_a?(SanitizedParameter) + class SanitizedField < SanitizedParameter + def initialize(name, field_type, field_params, endian) + @name = name + @prototype = SanitizedPrototype.new(field_type, field_params, endian) end - def all_sanitized? - @all_sanitized + attr_reader :prototype + + def name_as_sym + @name.nil? ? nil : @name.to_sym end - def sanitize!(sanitizer) - unless @all_sanitized - merge_default_parameters! + def name + @name + end - @the_class.sanitize_parameters!(self, sanitizer) + def instantiate(value = nil, parent = nil) + @prototype.instantiate(value, parent) + end + end + #---------------------------------------------------------------------------- - ensure_mandatory_parameters_exist - ensure_mutual_exclusion_of_parameters + class SanitizedFields < SanitizedParameter + def initialize(endian) + @fields = [] + @endian = endian + end + attr_reader :fields - @all_sanitized = true - end + def add_field(type, name, params) + name = nil if name == "" + + @fields << SanitizedField.new(name, type, params, @endian) end - def move_unknown_parameters_to(dest) - unless @all_sanitized - unused_keys = keys - @the_class.accepted_parameters.all - unused_keys.each do |key| - dest[key] = delete(key) - end - end + def [](idx) + @fields[idx] end - def warn_replacement_parameter(bad_key, suggested_key) - if has_parameter?(bad_key) - warn ":#{bad_key} is not used with #{@the_class}. " + - "You probably want to change this to :#{suggested_key}" - end + def empty? + @fields.empty? end - #--------------- - private + def length + @fields.length + end - def ensure_no_nil_values - each do |key, value| - if value.nil? - raise ArgumentError, - "parameter '#{key}' has nil value in #{@the_class}" - end - end + def each(&block) + @fields.each(&block) end - def merge_default_parameters! - @the_class.default_parameters.each do |key, value| - self[key] ||= value - end + def collect(&block) + @fields.collect(&block) end - def ensure_mandatory_parameters_exist - @the_class.mandatory_parameters.each do |key| - unless has_parameter?(key) - raise ArgumentError, - "parameter '#{key}' must be specified in #{@the_class}" - end - end + def field_names + @fields.collect { |field| field.name_as_sym } end - def ensure_mutual_exclusion_of_parameters - return if length < 2 + def has_field_name?(name) + @fields.detect { |f| f.name_as_sym == name.to_sym } + end - @the_class.mutually_exclusive_parameters.each do |key1, key2| - if has_parameter?(key1) and has_parameter?(key2) - raise ArgumentError, "params '#{key1}' and '#{key2}' " + - "are mutually exclusive in #{@the_class}" - end - end + def all_field_names_blank? + @fields.all? { |f| f.name == nil } end + def no_field_names_blank? + @fields.all? { |f| f.name != nil } + end + + def copy_fields(other) + @fields.concat(other.fields) + end end #---------------------------------------------------------------------------- - # The Sanitizer sanitizes the parameters that are passed when creating a - # BinData object. Sanitizing consists of checking for mandatory, optional - # and default parameters and ensuring the values of known parameters are - # valid. - class Sanitizer - - class << self - # Sanitize +params+ for +the_class+. - # Returns sanitized parameters. - def sanitize(params, the_class) - if params.is_a?(SanitizedParameters) and params.all_sanitized? - params + class SanitizedChoices < SanitizedParameter + def initialize(choices, endian) + @choices = {} + choices.each_pair do |key, val| + if SanitizedParameter === val + prototype = val else - sanitizer = self.new - sanitizer.create_sanitized_params(params, the_class) + type, param = val + prototype = SanitizedPrototype.new(type, param, endian) end + + @choices[key] = prototype end end - def initialize - @endian = nil + def [](key) + @choices[key] end + end + #---------------------------------------------------------------------------- - def create_sanitized_params(params, the_class) - sanitized_params = as_sanitized_params(params, the_class) - sanitized_params.sanitize!(self) + class SanitizedBigEndian < SanitizedParameter + def endian + :big + end + end - sanitized_params + class SanitizedLittleEndian < SanitizedParameter + def endian + :little end + end + #---------------------------------------------------------------------------- - def create_sanitized_endian(endian) - # memoize return value to reduce memory usage - if endian == :big - @@sbe ||= SanitizedBigEndian.new - elsif endian == :little - @@sle ||= SanitizedLittleEndian.new - else - raise ArgumentError, "unknown value for endian '#{endian}'" + # BinData objects are instantiated with parameters to determine their + # behaviour. These parameters must be sanitized to ensure their values + # are valid. When instantiating many objects with identical parameters, + # such as an array of records, there is much duplicated sanitizing. + # + # The purpose of the sanitizing code is to eliminate the duplicated + # validation. + # + # SanitizedParameters is a hash-like collection of parameters. Its purpose + # is to recursively sanitize the parameters of an entire BinData object chain + # at a single time. + class SanitizedParameters < Hash + + # Memoized constants + BIG_ENDIAN = SanitizedBigEndian.new + LITTLE_ENDIAN = SanitizedLittleEndian.new + + class << self + def sanitize(parameters, the_class) + if SanitizedParameters === parameters + parameters + else + SanitizedParameters.new(parameters, the_class, nil) + end end end - def create_sanitized_choices(choices) - SanitizedChoices.new(self, choices) - end + def initialize(parameters, the_class, endian) + parameters.each_pair { |key, value| self[key.to_sym] = value } - def create_sanitized_fields(fields = nil) - new_fields = SanitizedFields.new(self) - new_fields.copy_fields(fields) if fields - new_fields + @the_class = the_class + @endian = endian + + sanitize! end - def create_sanitized_object_prototype(obj_type, obj_params, endian = nil) - SanitizedPrototype.new(self, obj_type, obj_params, endian) + alias_method :has_parameter?, :has_key? + + def needs_sanitizing?(key) + parameter = self[key] + + parameter and not parameter.is_a?(SanitizedParameter) end - def with_endian(endian, &block) - if endian != nil - saved_endian = @endian - @endian = endian.respond_to?(:endian) ? endian.endian : endian - yield - @endian = saved_endian - else - yield + def warn_replacement_parameter(bad_key, suggested_key) + if has_parameter?(bad_key) + warn ":#{bad_key} is not used with #{@the_class}. " + + "You probably want to change this to :#{suggested_key}" end end - def lookup_class(type) - RegisteredClasses.lookup(type, @endian) + def endian + @endian || self[:endian] end + attr_writer :endian - #--------------- - private - - def as_sanitized_params(params, the_class) - if SanitizedParameters === params - params + def create_sanitized_endian(endian) + if endian == :big + BIG_ENDIAN + elsif endian == :little + LITTLE_ENDIAN else - SanitizedParameters.new(params || {}, the_class) + raise ArgumentError, "unknown value for endian '#{endian}'" end end - end - #---------------------------------------------------------------------------- - class SanitizedParameter; end - - class SanitizedPrototype < SanitizedParameter - def initialize(sanitizer, obj_type, obj_params, endian) - sanitizer.with_endian(endian) do - @obj_class = sanitizer.lookup_class(obj_type) - @obj_params = sanitizer.create_sanitized_params(obj_params, @obj_class) - end + def create_sanitized_params(params, the_class) + SanitizedParameters.new(params, the_class, self.endian) end - def instantiate(value = nil, parent = nil) - @factory ||= @obj_class.new(@obj_params) - - @factory.new(value, parent) + def create_sanitized_choices(choices) + SanitizedChoices.new(choices, self.endian) end - end - #---------------------------------------------------------------------------- - class SanitizedField < SanitizedParameter - def initialize(sanitizer, name, field_type, field_params, endian) - @name = (name != nil and name != "") ? name.to_s : nil - @prototype = sanitizer.create_sanitized_object_prototype(field_type, field_params, endian) + def create_sanitized_fields + SanitizedFields.new(self.endian) end - attr_reader :name - def instantiate(value = nil, parent = nil) - @prototype.instantiate(value, parent) + def create_sanitized_object_prototype(obj_type, obj_params) + SanitizedPrototype.new(obj_type, obj_params, self.endian) end - end - #---------------------------------------------------------------------------- - class SanitizedFields < SanitizedParameter - def initialize(sanitizer) - @sanitizer = sanitizer - @fields = [] - @field_names = nil - end - attr_reader :fields + #--------------- + private - def add_field(type, name, params, endian) - @field_names = nil - @fields << SanitizedField.new(@sanitizer, name, type, params, endian) - end + def sanitize! + ensure_no_nil_values + merge_default_parameters! - def [](idx) - @fields[idx] - end + @the_class.sanitize_parameters!(self) - def empty? - @fields.empty? + ensure_mandatory_parameters_exist + ensure_mutual_exclusion_of_parameters end - def field_names - # memoize field names to reduce duplicate copies - @field_names ||= @fields.collect { |field| field.name } + def ensure_no_nil_values + each do |key, value| + if value.nil? + raise ArgumentError, + "parameter '#{key}' has nil value in #{@the_class}" + end + end end - def copy_fields(other) - @field_names = nil - @fields.concat(other.fields) + def merge_default_parameters! + @the_class.default_parameters.each do |key, value| + self[key] ||= value + end end - end - #---------------------------------------------------------------------------- - class SanitizedChoices < SanitizedParameter - def initialize(sanitizer, choices) - @choices = {} - choices.each_pair do |key, val| - type, param = val - prototype = sanitizer.create_sanitized_object_prototype(type, param) - @choices[key] = prototype + def ensure_mandatory_parameters_exist + @the_class.mandatory_parameters.each do |key| + unless has_parameter?(key) + raise ArgumentError, + "parameter '#{key}' must be specified in #{@the_class}" + end end end - def [](key) - @choices[key] + def ensure_mutual_exclusion_of_parameters + return if length < 2 + + @the_class.mutually_exclusive_parameters.each do |key1, key2| + if has_parameter?(key1) and has_parameter?(key2) + raise ArgumentError, "params '#{key1}' and '#{key2}' " + + "are mutually exclusive in #{@the_class}" + end + end end end #---------------------------------------------------------------------------- - class SanitizedBigEndian < SanitizedParameter - def endian - :big - end - end - - class SanitizedLittleEndian < SanitizedParameter - def endian - :little - end - end end