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