lib/bindata/sanitize.rb in bindata-1.1.0 vs lib/bindata/sanitize.rb in bindata-1.2.0

- old
+ new

@@ -1,44 +1,53 @@ require 'bindata/registry' module BinData - class UnknownTypeError < StandardError ; end - - # A BinData object accepts arbitrary parameters. This class sanitizes - # those parameters so they can be used by the BinData object. + # 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 - def initialize(params, the_class) + def initialize(parameters, the_class) @all_sanitized = false @the_class = the_class @parameters = {} - params.each { |param, value| @parameters[param.to_sym] = value } + parameters.each { |key, value| @parameters[key.to_sym] = value } ensure_no_nil_values end def length @parameters.size end alias_method :size, :length - def [](param) - @parameters[param] + def [](key) + @parameters[key] end - def []=(param, value) - @parameters[param] = value unless @all_sanitized + def []=(key, value) + @parameters[key] = value unless @all_sanitized end - def has_parameter?(param) - @parameters.has_key?(param) + def has_parameter?(key) + @parameters.has_key?(key) end - def needs_sanitizing?(param) - has_parameter?(param) and not self[param].is_a?(SanitizedParameter) + def needs_sanitizing?(key) + parameter = @parameters[key] + + parameter and not parameter.is_a?(SanitizedParameter) end def all_sanitized? @all_sanitized end @@ -57,54 +66,56 @@ end def move_unknown_parameters_to(dest) unless @all_sanitized unused_keys = @parameters.keys - @the_class.accepted_parameters.all - unused_keys.each do |param| - next if param == :onlyif - dest[param] = @parameters.delete(param) + unused_keys.each do |key| + dest[key] = @parameters.delete(key) end end end - def delete(param) - @parameters.delete(param) + 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 #--------------- private def ensure_no_nil_values - @parameters.each do |param, value| + @parameters.each do |key, value| if value.nil? raise ArgumentError, - "parameter '#{param}' has nil value in #{@the_class}" + "parameter '#{key}' has nil value in #{@the_class}" end end end def merge_default_parameters! - @the_class.default_parameters.each do |param, value| - self[param] ||= value + @the_class.default_parameters.each do |key, value| + @parameters[key] ||= value end end def ensure_mandatory_parameters_exist - @the_class.mandatory_parameters.each do |param| - unless has_parameter?(param) + @the_class.mandatory_parameters.each do |key| + unless has_parameter?(key) raise ArgumentError, - "parameter '#{param}' must be specified in #{@the_class}" + "parameter '#{key}' must be specified in #{@the_class}" end end end def ensure_mutual_exclusion_of_parameters return if length < 2 - @the_class.mutually_exclusive_parameters.each do |param1, param2| - if has_parameter?(param1) and has_parameter?(param2) - raise ArgumentError, "params '#{param1}' and '#{param2}' " + + @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 @@ -140,48 +151,47 @@ sanitized_params end def create_sanitized_endian(endian) - SanitizedEndian.new(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}'" + end end def create_sanitized_choices(choices) SanitizedChoices.new(self, choices) end - def create_sanitized_fields - SanitizedFields.new(self) - end - - def clone_sanitized_fields(fields) + def create_sanitized_fields(fields = nil) new_fields = SanitizedFields.new(self) - new_fields.copy_fields(fields) + new_fields.copy_fields(fields) if fields new_fields end def create_sanitized_object_prototype(obj_type, obj_params, endian = nil) SanitizedPrototype.new(self, obj_type, obj_params, endian) end def with_endian(endian, &block) if endian != nil saved_endian = @endian - @endian = endian.is_a?(SanitizedEndian) ? endian.endian : endian + @endian = endian.respond_to?(:endian) ? endian.endian : endian yield @endian = saved_endian else yield end end def lookup_class(type) - registered_class = RegisteredClasses.lookup(type, @endian) - if registered_class.nil? - raise UnknownTypeError, type.to_s - end - registered_class + RegisteredClasses.lookup(type, @endian) end #--------------- private @@ -226,27 +236,35 @@ class SanitizedFields < SanitizedParameter def initialize(sanitizer) @sanitizer = sanitizer @fields = [] + @field_names = nil end + attr_reader :fields def add_field(type, name, params, endian) + @field_names = nil @fields << SanitizedField.new(@sanitizer, name, type, params, endian) end def [](idx) @fields[idx] end + def empty? + @fields.empty? + end + def field_names - @fields.collect { |field| field.name } + # memoize field names to reduce duplicate copies + @field_names ||= @fields.collect { |field| field.name } end def copy_fields(other) - other_fields = other.instance_variable_get(:@fields) - @fields.concat(other_fields) + @field_names = nil + @fields.concat(other.fields) end end #---------------------------------------------------------------------------- class SanitizedChoices < SanitizedParameter @@ -263,17 +281,17 @@ @choices[key] end end #---------------------------------------------------------------------------- - class SanitizedEndian < SanitizedParameter - def initialize(endian) - unless [:little, :big].include?(endian) - raise ArgumentError, "unknown value for endian '#{endian}'" - end - - @endian = endian + class SanitizedBigEndian < SanitizedParameter + def endian + :big end + end - attr_reader :endian + class SanitizedLittleEndian < SanitizedParameter + def endian + :little + end end end