lib/bindata/base.rb in bindata-0.9.3 vs lib/bindata/base.rb in bindata-0.10.0
- old
+ new
@@ -14,15 +14,10 @@
# == Parameters
#
# Parameters may be provided at initialisation to control the behaviour of
# an object. These params are:
#
- # [<tt>:readwrite</tt>] Deprecated. An alias for :onlyif.
- # [<tt>:onlyif</tt>] Used to indicate a data object is optional.
- # if false, calls to #read or #write will not
- # perform any I/O, #num_bytes will return 0 and
- # #snapshot will return nil. Default is true.
# [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't
# meet this criteria. A boolean return indicates
# success or failure. Any other return is compared
# to the current offset. The variable +offset+
# is made available to any lambda assigned to
@@ -33,98 +28,53 @@
# <tt>:check_offset</tt>, except that it will
# adjust the IO offset instead of raising an error.
class Base
class << self
- extend Parameters
- # Define methods for:
- # bindata_mandatory_parameters
- # bindata_optional_parameters
- # bindata_default_parameters
- # bindata_mutually_exclusive_parameters
-
- define_x_parameters(:bindata_mandatory, []) do |array, args|
- args.each { |arg| array << arg.to_sym }
- array.uniq!
+ # Instantiates this class and reads from +io+, returning the newly
+ # created data object.
+ def read(io)
+ data = self.new
+ data.read(io)
+ data
end
- define_x_parameters(:bindata_optional, []) do |array, args|
- args.each { |arg| array << arg.to_sym }
- array.uniq!
+ def recursive?
+ # data objects do not self reference by default
+ false
end
- define_x_parameters(:bindata_default, {}) do |hash, args|
- params = args.length > 0 ? args[0] : {}
- hash.merge!(params)
- end
+ AcceptedParameters.define_all_accessors(self, :internal)
- define_x_parameters(:bindata_mutually_exclusive, []) do |array, args|
- array << [args[0].to_sym, args[1].to_sym]
+ def accepted_internal_parameters
+ internal = AcceptedParameters.get(self, :internal)
+ internal.all
end
- # Returns a list of internal parameters that are accepted by this object
- def internal_parameters
- (bindata_mandatory_parameters + bindata_optional_parameters +
- bindata_default_parameters.keys).uniq
+ def sanitize_parameters!(sanitizer, params)
+ internal = AcceptedParameters.get(self, :internal)
+ internal.sanitize_parameters!(sanitizer, params)
end
- # Ensures that +params+ is of the form expected by #initialize.
- def sanitize_parameters!(sanitizer, params)
- # replace :readwrite with :onlyif
- if params.has_key?(:readwrite)
- warn ":readwrite is deprecated. Replacing with :onlyif"
- params[:onlyif] = params.delete(:readwrite)
- end
+ #-------------
+ private
- # add default parameters
- bindata_default_parameters.each do |k,v|
- params[k] = v unless params.has_key?(k)
+ def warn_replacement_parameter(params, bad_key, suggested_key)
+ if params.has_key?(bad_key)
+ warn ":#{bad_key} is not used with #{self}. " +
+ "You probably want to change this to :#{suggested_key}"
end
-
- # ensure mandatory parameters exist
- bindata_mandatory_parameters.each do |prm|
- if not params.has_key?(prm)
- raise ArgumentError, "parameter ':#{prm}' must be specified " +
- "in #{self}"
- end
- end
-
- # ensure mutual exclusion
- bindata_mutually_exclusive_parameters.each do |param1, param2|
- if params.has_key?(param1) and params.has_key?(param2)
- raise ArgumentError, "params #{param1} and #{param2} " +
- "are mutually exclusive"
- end
- end
end
- # Can this data object self reference itself?
- def recursive?
- false
+ def register(name, class_to_register)
+ RegisteredClasses.register(name, class_to_register)
end
-
- # Instantiates this class and reads from +io+. For single value objects
- # just the value is returned, otherwise the newly created data object is
- # returned.
- def read(io)
- data = self.new
- data.read(io)
- data.single_value? ? data.value : data
- end
-
- # Registers the mapping of +name+ to +klass+.
- def register(name, klass)
- Registry.instance.register(name, klass)
- end
- private :register
end
- # Define the parameters we use in this class.
- bindata_optional_parameters :check_offset, :adjust_offset
- bindata_default_parameters :onlyif => true
- bindata_mutually_exclusive_parameters :check_offset, :adjust_offset
+ optional_parameters :check_offset, :adjust_offset
+ mutually_exclusive_parameters :check_offset, :adjust_offset
# Creates a new data object.
#
# +params+ is a hash containing symbol keys. Some params may
# reference callable objects (methods or procs). +parent+ is the
@@ -133,153 +83,166 @@
def initialize(params = {}, parent = nil)
@params = Sanitizer.sanitize(self.class, params)
@parent = parent
end
- # The parent data object.
- attr_accessor :parent
+ attr_reader :parent
- # Returns all the custom parameters supplied to this data object.
- def parameters
- @params.extra_parameters
+ # Returns the result of evaluating the parameter identified by +key+.
+ # +overrides+ is an optional +parameters+ like hash that allow the
+ # parameters given at object construction to be overridden.
+ # Returns nil if +key+ does not refer to any parameter.
+ def eval_parameter(key, overrides = {})
+ LazyEvaluator.eval(self, get_parameter(key), overrides)
end
- # Reads data into this data object by calling #do_read then #done_read.
+ # Returns the parameter referenced by +key+.
+ # Use this method if you are sure the parameter is not to be evaluated.
+ # You most likely want #eval_parameter.
+ def get_parameter(key)
+ @params[key]
+ end
+
+ # Returns whether +key+ exists in the +parameters+ hash.
+ def has_parameter?(key)
+ @params.has_key?(key)
+ end
+
+ # Reads data into this data object.
def read(io)
io = BinData::IO.new(io) unless BinData::IO === io
do_read(io)
done_read
self
end
- # Reads the value for this data from +io+.
def do_read(io)
- raise ArgumentError, "io must be a BinData::IO" unless BinData::IO === io
-
+ check_or_adjust_offset(io)
clear
- check_offset(io)
+ _do_read(io)
+ end
- if eval_param(:onlyif)
- _do_read(io)
- end
+ def done_read
+ _done_read
end
+ protected :do_read, :done_read
- # Writes the value for this data to +io+ by calling #do_write.
+ # Writes the value for this data to +io+.
def write(io)
io = BinData::IO.new(io) unless BinData::IO === io
do_write(io)
io.flush
self
end
-
- # Writes the value for this data to +io+.
def do_write(io)
- raise ArgumentError, "io must be a BinData::IO" unless BinData::IO === io
-
- if eval_param(:onlyif)
- _do_write(io)
- end
+ _do_write(io)
end
+ protected :do_write
- # Returns the number of bytes it will take to write this data by calling
- # #do_num_bytes.
+ # Returns the number of bytes it will take to write this data.
def num_bytes(what = nil)
num = do_num_bytes(what)
num.ceil
end
- # Returns the number of bytes it will take to write this data.
def do_num_bytes(what = nil)
- if eval_param(:onlyif)
- _do_num_bytes(what)
- else
- 0
- end
+ _do_num_bytes(what)
end
+ protected :do_num_bytes
+ # Assigns the value of +val+ to this data object. Note that +val+ will
+ # always be deep copied to ensure no aliasing problems can occur.
+ def assign(val)
+ _assign(val)
+ end
+
# Returns a snapshot of this data object.
- # Returns nil if :onlyif is false
def snapshot
- if eval_param(:onlyif)
- _snapshot
- else
- nil
- end
+ _snapshot
end
# Returns the string representation of this data object.
- def to_s
+ def to_binary_s
io = StringIO.new
write(io)
io.rewind
io.read
end
- # Return a human readable representation of this object.
+ # Return a human readable representation of this data object.
def inspect
snapshot.inspect
end
- # Returns the object this object represents.
- def obj
- self
+ # Return a string representing this data object.
+ def to_s
+ snapshot.to_s
end
- #---------------
- private
+ # Returns a user friendly name of this object for debugging purposes.
+ def debug_name
+ if parent
+ parent.debug_name_of(self)
+ else
+ "obj"
+ end
+ end
- # Returns the value of the evaluated parameter. +key+ references a
- # parameter from the +params+ hash used when creating the data object.
- # +values+ contains data that may be accessed when evaluating +key+.
- # Returns nil if +key+ does not refer to any parameter.
- def eval_param(key, values = nil)
- LazyEvaluator.eval(no_eval_param(key), self, values)
+ # Returns the offset of this object wrt to its most distant ancestor.
+ def offset
+ if parent
+ parent.offset_of(self)
+ else
+ 0
+ end
end
- # Returns the parameter from the +params+ hash referenced by +key+.
- # Use this method if you are sure the parameter is not to be evaluated.
- # You most likely want #eval_param.
- def no_eval_param(key)
- @params.internal_parameters[key]
+ def ==(other)
+ # double dispatch
+ other == snapshot
end
- # Returns whether +key+ exists in the +params+ hash used when creating
- # this data object.
- def has_param?(key)
- @params.internal_parameters.has_key?(key)
+ #---------------
+ private
+
+ def check_or_adjust_offset(io)
+ if has_parameter?(:check_offset)
+ check_offset(io)
+ elsif has_parameter?(:adjust_offset)
+ adjust_offset(io)
+ end
end
- # Checks that the current offset of +io+ is as expected. This should
- # be called from #do_read before performing the reading.
def check_offset(io)
- if has_param?(:check_offset)
- actual_offset = io.offset
- expected = eval_param(:check_offset, :offset => actual_offset)
+ actual_offset = io.offset
+ expected = eval_parameter(:check_offset, :offset => actual_offset)
- if not expected
- raise ValidityError, "offset not as expected"
- elsif actual_offset != expected and expected != true
- raise ValidityError, "offset is '#{actual_offset}' but " +
- "expected '#{expected}'"
+ if not expected
+ raise ValidityError, "offset not as expected for #{debug_name}"
+ elsif actual_offset != expected and expected != true
+ raise ValidityError,
+ "offset is '#{actual_offset}' but " +
+ "expected '#{expected}' for #{debug_name}"
+ end
+ end
+
+ def adjust_offset(io)
+ actual_offset = io.offset
+ expected = eval_parameter(:adjust_offset)
+ if actual_offset != expected
+ begin
+ seek = expected - actual_offset
+ io.seekbytes(seek)
+ warn "adjusting stream position by #{seek} bytes" if $VERBOSE
+ rescue
+ raise ValidityError,
+ "offset is '#{actual_offset}' but couldn't seek to " +
+ "expected '#{expected}' for #{debug_name}"
end
- elsif has_param?(:adjust_offset)
- actual_offset = io.offset
- expected = eval_param(:adjust_offset)
- if actual_offset != expected
- begin
- seek = expected - actual_offset
- io.seekbytes(seek)
- warn "adjusting stream position by #{seek} bytes" if $VERBOSE
- rescue
- # could not seek so raise an error
- raise ValidityError, "offset is '#{actual_offset}' but " +
- "couldn't seek to expected '#{expected}'"
- end
- end
end
end
###########################################################################
# To be implemented by subclasses
@@ -292,43 +255,61 @@
# Returns true if the object has not been changed since creation.
def clear?(*args)
raise NotImplementedError
end
- # Returns whether this data object contains a single value. Single
- # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
- def single_value?
+ # Returns the debug name of +child+. This only needs to be implemented
+ # by objects that contain child objects.
+ def debug_name_of(child)
raise NotImplementedError
end
- # To be called after calling #do_read.
- def done_read
+ # Returns the offset of +child+. This only needs to be implemented
+ # by objects that contain child objects.
+ def offset_of(child)
raise NotImplementedError
end
# Reads the data for this data object from +io+.
def _do_read(io)
raise NotImplementedError
end
+ # Trigger function that is called after #do_read.
+ def _done_read
+ raise NotImplementedError
+ end
+
# Writes the value for this data to +io+.
def _do_write(io)
raise NotImplementedError
end
# Returns the number of bytes it will take to write this data.
- def _do_num_bytes
+ def _do_num_bytes(what)
raise NotImplementedError
end
+ # Assigns the value of +val+ to this data object. Note that +val+ will
+ # always be deep copied to ensure no aliasing problems can occur.
+ def _assign(val)
+ raise NotImplementedError
+ end
+
# Returns a snapshot of this data object.
def _snapshot
raise NotImplementedError
end
# Set visibility requirements of methods to implement
- public :clear, :clear?, :single_value?, :done_read
- private :_do_read, :_do_write, :_do_num_bytes, :_snapshot
+ public :clear, :clear?, :debug_name_of, :offset_of
+ private :_do_read, :_done_read, :_do_write, :_do_num_bytes, :_assign, :_snapshot
+
+def single_value?
+ warn "#single_value? is deprecated. It should no longer be needed"
+ false
+end
+public :single_value?
# End To be implemented by subclasses
###########################################################################
end
end