class memmapfile
%MEMMAPFILE Construct memory-mapped file object.
%   M = MEMMAPFILE(FILENAME) constructs a memmapfile object that maps file
%   FILENAME to memory, using default property values. FILENAME can be a partial
%   pathname relative to the MATLAB path. If the file is not found in or
%   relative to the current working directory, MEMMAPFILE searches down the
%   MATLAB search path.
%    
%   M = MEMMAPFILE(FILENAME, PROP1, VALUE1, PROP2, VALUE2, ...) constructs a
%   memmapfile object, and sets the properties of that object that are named in
%   the argument list (PROP1, PROP2, etc.) to the given values (VALUE1, VALUE2,
%   etc.). All property name arguments must be quoted strings (e.g.,
%   'writable'). Any properties that are not specified are given their default
%   values.
%
%   Property/Value pairs and descriptions:
%
%       Format: Char array or Nx3 cell array (defaults to 'uint8').
%           Format of the contents of the mapped region. 
%
%           If a char array, Format specifies that the mapped data is to be
%           accessed as a single vector of type specified by Format's
%           value. Supported char arrays are 'int8', 'int16', 'int32', 'int64', 
%           'uint8', 'uint16', 'uint32', 'uint64', 'single', and 'double'.
%
%           If an Nx3 cell array, Format specifies that the mapped data is to be
%           accessed as a repeating series of segments of basic types, each with
%           specific dimensions and name. The cell array must be of the form
%           {TYPE1, DIMS1, NAME1; ...; TYPEn, DIMSn, NAMEn}, where TYPE is one
%           of the data type strings listed above, DIMS is a numeric row vector
%           specifying the dimensions of the segment of data to use, and NAME is
%           a char string specifying the field name to use to access the data
%           (as a subfield of the Data property). See Data property and examples
%           below.
%
%       Repeat: Positive integer or Inf (defaults to Inf).
%           Number of times to apply the specified format to the mapped
%           region of the file. If Inf, repeat until end of file. 
%
%       Offset: Nonnegative integer (defaults to 0).
%           Number of bytes from the start of the file to the start of the
%           mapped region. Offset 0 represents the start of the file.
%
%       Writable: True or false (defaults to false).
%           Access level which determines whether or not Data property (see
%           below) may be assigned to.
%
%   All the properties above may also be accessed after the memmapfile object
%   has been created by dot-subscripting the memmapfile object. For example,
%
%       M.writable = true;
% 
%   changes the Writable property of M to true.
%
%   Two properties which may not be specified to the MEMMAPFILE constrcutor as
%   Property/Value pairs are listed below. These may be accessed (with
%   dot-subscripting) after the memmapfile object has been created.
%
%       Data: Numeric array or structure array.
%           Contains the actual memory-mapped data from FILENAME. If Format is a
%           char array, then Data is a simple numeric array of the type
%           specified by Format. If Format is a cell array, then Data is a
%           structure array, the field names of which are specified by the third
%           column of the cell array. The type and shape of each field of Data
%           are determined by the first and second columns of the cell array,
%           respectively. Changes to the Data field or subfields also change the
%           corresponding values in the memory-mapped file.
%
%       Filename: Char array.
%           Contains the name of the file being mapped.
%
%   Note that when a variable containing a memmapfile object goes out of scope
%   or is otherwise cleared, the memory map is automatically closed. There is no
%   need to call the DELETE method or any other memmapfile cleanup function.
%
%   Examples:
%       % To map the file 'records.dat' to a series of unsigned 32-bit
%       % integers and set every other value to zero (in Data and
%       % records.dat): 
%       m = memmapfile('records.dat', 'format', 'uint32', 'writable', true);
%       m.data(1:2:end) = 0;
%
%       % To map the file 'records.dat' to a repeating series of 20 singles
%       % (as a 5-by-4 matrix) called 'sdata', followed by 10 doubles (as a
%       1-by-10 vector) called 'ddata': 
%       m = memmapfile('records.dat', 'format', {'single' [5 4] 'sdata'; ...
%                                                'double', [1 10] 'ddata'});
%       firstSdata = m.data(1).sdata;
%       firstDdata = m.data(1).ddata;
%
%   See also MEMMAPFILE/DISP, MEMMAPFILE/GET, MEMMAPFILE/SUBSASGN,
%   MEMMAPFILE/SUBSREF.

%   Copyright 2004 The MathWorks, Inc.
%   $Revision: 1.1.6.1.2.1 $  $Date: 2005/01/18 16:06:44 $

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%   PROPERTIES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Accessible properties of the memory-mapped object
properties
writable = false;
    function v = set(obj, v)
    if ~isscalar(v) || ~isa(v, 'logical')
        error('MATLAB:memmapfile:illegalWritableSetting', ...
              'The Writable field must contain a scalar logical.');
    end
    end % set(writable)
    
offset   = 0;
    function v = set(obj, v)
    if ~isscalar(v) || ~isnumeric(v) || ~isreal(v)
        error('MATLAB:memmapfile:illegalOffsetType', ...
              'The Offset field must be a real scalar number.')
    elseif ~isfinite(v) || v < 0 || (~isinteger(v) && v ~= fix(v))
        error('MATLAB:memmapfile:illegalOffsetValue', ...
              'The Offset field must contain a finite, nonnegative integral value.');
    end
    v = double(v);
    end % set(offset)

format   = 'uint8';
    function v = set(obj, v)
    if ~hIsValidFormat(v)
        error('MATLAB:memmapfile:illegalFormatSetting', ...
              'The Format field specified is invalid.');
    end
    end % set(format)
    
repeat   = inf;
    function v = set(obj, v)
    if ~isscalar(v) || ~isnumeric(v) || ~isreal(v)
        error('MATLAB:memmapfile:illegalRepeatType', ...
              'The Repeat field must contain a real scalar number.');
    elseif isnan(v) || v <= 0 || (~isinteger(v) && v ~= fix(v))
        error('MATLAB:memmapfile:illegalRepeatValue', ...
              'The Repeat field must contain a positive integral value, or Inf.');
    end
    v = double(v);
    end % set(repeat)

filename = '';
    
% Private property of the memory-mapped object
% NOTE: All private properties should have mixed case names so that they are not
% found by @memmapfile/subsref and @memmapfile/subsasgn, which simply get/set
% obj.(lower(fieldname)).
properties (SetAccess='private', GetAccess='private')

fileSize = 0; % size of file.

properties (SetAccess='private', GetAccess='private', Transient=true)

checkAlignmentNeeded = any(strcmp(computer, {'SOL2', 'HPUX'}));

dataHandle     = 0;
    function v = set(obj, v)
    % Need to bump internal reference count of dataHandle if MATLAB is making this
    % object a copy of another object. That happens when this object's
    % dataHandle is [], and v is _not_ 0. If dataHandle is not empty, then we
    % are updating the handle internally (due to an unmap).  If v is 0, then we
    % are constructing the object from scratch, or loading the object from disk,
    % since dataHandle is transient.
    if isempty(obj.dataHandle) 
        if ~isequal(v, 0) && hIsMapped(v)
            hIncrementDataHandleRefCount(v);
        else
            % There's no memory map to share, so create a new dataHandle.
            v = hCreateNewDataHandle();
        end
    end
    end % set_dataHandle
        

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%   METHODS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

methods (Access='private')

% -------------------------------------------------------------------------
function hCreateMap(obj)

if obj.checkAlignmentNeeded && hIsUnaligned(obj.format, obj.offset, obj.repeat)
    error('MATLAB:memmapfile:illegalAlignment', ...
          ['Illegal format, offset, or repeat. Accessing the Data field ', ...
           'given the\nspecified format, offset, ', ...
           'and repeat values would cause an unaligned\naddress exception.']);
end

if isinf(obj.repeat)
    repeat = 1;
else
    repeat = obj.repeat;
end

if hFrameSize(obj.format) * repeat + obj.offset > obj.fileSize
    error('MATLAB:memmapfile:fileTooSmall', ...
          ['File "%s" is not large enough to map with the current format, offset, ' ...
           'and repeat values.'], obj.filename);
end

mapfileInputStruct.filename = obj.filename;
mapfileInputStruct.writable = obj.writable;
if obj.repeat == Inf
    mapfileInputStruct.numElements = 0;
else
    mapfileInputStruct.numElements = obj.repeat;
end
mapfileInputStruct.offset = obj.offset;
mapfileInputStruct.format = obj.format;
mapfileInputStruct.dataHandle = obj.dataHandle;

hMapFile(mapfileInputStruct);

end % hCreateMap

% -------------------------------------------------------------------------
function dataHandle = hDestroyMap(obj)
% unmap the file. If there are other references to the map, replace our
% dataHandle with a new empty handle. (hUnmapFile returns the number of
% references remaining on obj.dataHandle.) Otherwise, unmapping will clear out
% the existing dataHandle.
if hIsMapped(obj.dataHandle) && hUnmapFile(obj.dataHandle) > 0
    dataHandle = hCreateNewDataHandle();
else
    dataHandle = obj.dataHandle;
end
end % hDestroyMap

% -------------------------------------------------------------------------
function siz = hGetDataSize(obj)
framesAvailable = fix((obj.fileSize - obj.offset) / hFrameSize(obj.format));
if obj.repeat == Inf
    siz = [max(framesAvailable, 0) 1];
elseif framesAvailable < obj.repeat
    siz = [0 1];
else
    siz = [obj.repeat 1];
end
end % hGetDataSize

% -------------------------------------------------------------------------
% Update filename field of memmapfile obj. 
%  * Make sure file is accessible. 
%  * Replace filename with a full path version if it refers to a file on the matlabpath 
%    or is a partial path name. 
%  * Recompute cached size of file.
function obj = hChangeFilename(obj, filename)

% Validate type of filename
if ~ischar(filename) || ~isvector(filename) || size(filename, 1) ~= 1
    error('MATLAB:memmapfile:illegalFilenameType', 'Filename must be a string.');
end

[fid, reason] = fopen(filename);
if fid == -1
    error('MATLAB:memmapfile:inaccessibleFile', ...
          'Cannot access file "%s": %s.', filename, reason);
end

foundFilename = fopen(fid); % if file found on MATLABPATH, fopen will return full path
                            % to found file.

fseek(fid, 0, 'eof');
obj.fileSize = ftell(fid);
fclose(fid);

% Get full path name
obj.filename = hResolveFilename(filename, foundFilename);

end % hChangeFilename


% -------------------------------------------------------------------------
% Handle subsasgn to Data field when it contains a numeric array.
function [valid, s, newval] = hParseNumericDataSubsasgn(obj, s, newval)
valid = false; 
lenS = length(s);

if lenS == 1
    % X.DATA = NEWVAL
    LHS = subsref(obj, s); % get array being assigned to

    if prod(size(LHS)) == prod(size(newval))
        if strcmp(class(LHS), class(newval))
            % Map the operation to X.DATA(:,:) = F(NEWVAL) (where F reshapes NEWVAL suitably)
            [s, newval] = hMapToTwoColonIndices(LHS, s, newval);
            valid = true;
        else
            error('MATLAB:memmapfile:classMismatch', ...
                  'Values assigned to the Data field must have the same class as the Data field.');
        end
    else
        error('MATLAB:memmapfile:sizeMismatch', ...
              ['In an assignment M.DATA = N, the number of elements in N and ' ...
               'M.DATA must be the same.']);
    end
elseif s(2).type(1)=='('
    % X.DATA(INDS)? = NEWVAL
    if lenS == 2
        % X.DATA(INDS) = NEWVAL - this is legal and handled by hSubsasgn as is.

        % Check for out of bound and single-colon indices.
        LHS = subsref(obj, s(1)); % get array being assigned to
        if hSubsasgnIndexOutOfRange(LHS, s(2).subs) || ...
           hSubsasgnIsSubscriptedDeletion(s(2).subs, newval)
            error(hGetCommonMemmapfileError('MATLAB:memmapfile:dataFieldSizeFixed'));
        else
            [s, newval] = hFixSubscriptedColonAssignment(LHS, s, newval);
            valid = true;
        end
    end
end

end % hParseNumericDataSubsasgn

% -------------------------------------------------------------------------
% Handle subsasgn to Data field when it contains a structure array.
function [valid, s, newval] = hParseStructDataSubsasgn(obj, s, newval)
valid = false; % When this is set to true, LHS must have already been defined.

% x.data? = FOO
if length(s)==1
    % x.data = FOO
    error(hGetCommonMemmapfileError('MATLAB:memmapfile:illegalDataFieldModification'));
elseif s(2).type(1) == '.'
    % x.data.BAR? = FOO
    if length(s)==2
        % x.data.BAR = FOO
        % -  same as x.data(:).BAR = FOO  -
        s = [s(1) substruct('()', {':'}) s(2:end)];
    elseif s(3).type(1) == '('
        % x.data.BAR()? = FOO
        if length(s) == 3
            % x.data.BAR() = FOO
            % -  same as x.data(:).BAR() = FOO  -
            s = [s(1) substruct('()', {':'}) s(2:end)];
        end
    end
end

if s(2).type(1) == '('
    % x.data()? = FOO
    if length(s) == 2
        % x.data(inds) = FOO
        error(hGetCommonMemmapfileError('MATLAB:memmapfile:illegalDataFieldModification'));
    elseif s(3).type(1) == '.'
        % x.data().BAR? = FOO
        if length(s) == 3
            % x.data().BAR = FOO

            if hSubsasgnIndexOutOfRange(subsref(obj, s(1)), s(2).subs)
                error(hGetCommonMemmapfileError('MATLAB:memmapfile:dataFieldSizeFixed'));
            end
            LHS = subsref(obj, s); % get array being assigned to
            
            if prod(size(LHS)) == prod(size(newval))
                if strcmp(class(LHS), class(newval))
                    % Map the operation to X.DATA.BAR(:,:) = F(NEWVAL) (where F
                    % reshapes NEWVAL suitably.)
                    [s, newval] = hMapToTwoColonIndices(LHS, s, newval);
                    valid = true;
                else
                    error('MATLAB:memmapfile:classMismatchForSubfield', ...
                          ['Values assigned to subfields of the Data field must have the same ' ...
                           'class as the subfield.']);
                end
            else
                error('MATLAB:memmapfile:sizeMismatchForSubfield', ...
                      ['In an assignment M.DATA(I).FIELD = N, the number of elements ' ...
                       'in N and M.DATA(I).FIELD must be the same.']);
            end
        elseif s(4).type(1) == '('
            % x.data().BAR()? = FOO
            if length(s) == 4
                % x.data().BAR() = FOO
                % Check for out of bound and single-colon indices.
                if hSubsasgnIndexOutOfRange(subsref(obj, s(1)), s(2).subs)
                    error(hGetCommonMemmapfileError('MATLAB:memmapfile:dataFieldSizeFixed'));
                end
                LHS = subsref(obj, s(1:3)); % get array being assigned to

                if hSubsasgnIndexOutOfRange(LHS, s(4).subs) || ...
                   hSubsasgnIsSubscriptedDeletion(s(4).subs, newval)
                    error(hGetCommonMemmapfileError('MATLAB:memmapfile:dataSubfieldSizeFixed'));
                else
                    [s, newval] = hFixSubscriptedColonAssignment(LHS, s, newval);
                    valid = true;
                end
            end
        end
    end
end

end % hParseStructDataSubsasgn

% -------------------------------------------------------------------------
% Handle subsasgn to Data field.
function hDoDataSubsasgn(obj, s, newval)
if ischar(obj.format)
    [valid, s, newval] = hParseNumericDataSubsasgn(obj, s, newval);
else
    [valid, s, newval] = hParseStructDataSubsasgn(obj, s, newval);
end

if valid
    if isnumeric(newval) && ~isreal(newval)
        error('MATLAB:memmapfile:illegalComplexAssignment', ...
              'Complex values may not be assigned to Data field or its subfields.');
    end

    hSubsasgn(obj.dataHandle, s(2:end), newval);
else
    error('MATLAB:memmapfile:illegalSubscriptedAssignment', ...
          'Illegal subscripted assignment to Data field of memmapfile object.')
end



end % hDoDataSubsasgn

methods

% -------------------------------------------------------------------------

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%   Constructor 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function obj = memmapfile(filename, varargin)

error(nargchk(1, nargin, nargin, 'struct'));

if rem(length(varargin), 2) ~= 0
    error('MATLAB:memmapfile:UnpairedParamsValues', 'Param/value pairs must come in pairs.');
end

construct obj

obj = hChangeFilename(obj, filename);

% Parse param-value pairs
for i = 1:2:length(varargin)

    if ~ischar(varargin{i})
        error ('MATLAB:memmapfile:illegalParameter', ...
               'Parameter at input %d must be a string.', i);
    end
    
    fieldname = lower(varargin{i});
    switch fieldname
        case {'writable', 'offset', 'format', 'repeat'}
            obj.(fieldname) = varargin{i+1};
        otherwise
            error('MATLAB:memmapfile:illegalParameter', 'Parameter "%s" is unrecognized.', ...
                  varargin{i});
    end
end

end % Constructor

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%   Destructor
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function delete(obj)
% release this object's reference of its memory map. hDestroyMap returns a
% potentially modified dataHandle associated with obj, instead of a modified
% obj, because that would cause a new memmapfile object to be created while
% destroying the old one which would lead to an infinite loop.
dataHandle = hDestroyMap(obj);

% free the internal structure owned by this object.
hDeleteDataHandle(dataHandle);

end % Destructor (delete) method

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%   Sub-assignment method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function obj = subsasgn(obj, s, newval)

if s(1).type(1) ~= '.'
    if s(1).type(1) == '('
        error(hGetCommonMemmapfileError('MATLAB:memmapfile:illegalParenIndex'));
    else
        error(hGetCommonMemmapfileError('MATLAB:memmapfile:illegalBraceIndex'));
    end
else
    fieldname = lower(s(1).subs);
        
    if strcmp(fieldname, 'data')
        if ~obj.writable
            error('MATLAB:memmapfile:dataIsReadOnly', ...
                  'Cannot modify Data field because Writable field is set to false.');
        end

        if ~hIsMapped(obj.dataHandle)
            hCreateMap(obj);
        end
        
        hDoDataSubsasgn(obj, s, newval);
        
    elseif strcmp(fieldname, 'filename')
        if (length(s) > 1)
            newname = subsasgn(obj.filename, s(2:end), newval);
        else
            newname = newval;
        end
        
        obj = hChangeFilename(obj, newname);
    else
        if length(s) > 1
            obj.(fieldname) = subsasgn(obj.(fieldname), s(2:end), newval);
        else
            obj.(fieldname) = newval;
        end
    end
    
    if any(strcmp({'filename', 'format', 'offset', 'repeat', 'writable'}, fieldname))
        obj.dataHandle = hDestroyMap(obj);
    end
end

end % Subsassgn

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%   Sub-reference method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function val = subsref(obj, s)
%disp 'in subsref'

if (s(1).type(1) == '.')
    if strcmpi(s(1).subs, 'data')
        if ~hIsMapped(obj.dataHandle)
            hCreateMap(obj);
        end
        val = hSubsref(obj.dataHandle, s(2:end));
    else
        val = obj.(lower(s(1).subs));
        if length(s) > 1
            val = subsref(val, s(2:end));
        end
    end        
else
    if s(1).type(1) == '('
        error(hGetCommonMemmapfileError('MATLAB:memmapfile:illegalParenIndex'));
    else
        error(hGetCommonMemmapfileError('MATLAB:memmapfile:illegalBraceIndex'));
    end
end

end % Subsref

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%   Get method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function mInfo = get(obj, property)
if nargin == 1
    out.Filename = obj.filename;
    out.Writable = obj.writable;
    out.Offset = obj.offset;
    out.Format = obj.format;
    out.Repeat = obj.repeat;
    out.Data = subsref(obj, substruct('.', 'data'));

    if nargout == 0
        disp(out);
    else
        mInfo = out;
    end
else
    if ischar(property)
        lowerProperty = lower(property);
        switch lowerProperty
            case {'filename', 'format', 'writable', 'offset', 'repeat'}
                mInfo = obj.(lowerProperty);
                
            case 'data'
                mInfo = subsref(obj, substruct('.', 'data'));
                
            otherwise
                error('MATLAB:memmapfile:get:unknownProperty', ...
                      'There is no ''%s'' property in the ''memmapfile'' class.', ...
                      property);
        end
    elseif iscellstr(property)
        % Make sure property is a row vector.
        property = property(:)';
        mInfo = {};
        for i = 1:length(property)
            mInfo{i} = get(obj, property{i});
        end
    else
        error('MATLAB:memmapfile:illegalPropertyType', ...
              'Property must be a string or cell array of strings.');
    end
end

end % GET method

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%   Disp method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function disp(obj)
% %12s leaves exactly 4 spaces before the longest attribute, 'Filename', and
% lines the other strings up by the colon, like how struct display works.
fprintf(1, '%12s: ''%s''\n', 'Filename', obj.filename);
fprintf(1, '%12s: %s\n', 'Writable', mat2str(obj.writable));
fprintf(1, '%12s: %d\n', 'Offset', obj.offset);

fmt = obj.format;
if ischar(fmt)
    fprintf(1, '%12s: ''%s''\n', 'Format', fmt);
else
    fprintf(1, '%12s: {', 'Format');
    for i = 1:size(fmt, 1)
        if i > 1
           fprintf(1, '%15s', '');
        end
        
        fprintf(1, '''%s'' [', fmt{i,1});
        siz = fmt{i,2};
        fprintf(1, '%d ', siz(1:end-1));
        fprintf(1, '%d] ''%s''', siz(end), fmt{i,3});
        if i == size(fmt, 1)
            fprintf(1, '}');
        end
        fprintf('\n');
    end
end

fprintf(1, '%12s: %d\n', 'Repeat', obj.repeat);

% Don't print out all of Data, it could be really big. Print a summary instead.
fprintf(1, '%12s: ', 'Data');

siz = hGetDataSize(obj);

if iscell(obj.format)
    fprintf(1, '%dx%d struct array with ', siz);
    fprintf(1, 'fields:\n');
    fprintf(1, '%17s\n', obj.format{:,3});
else
    fprintf(1, '%dx%d %s array\n', siz, obj.format);
end 

if strcmp(get(0, 'FormatSpacing'), 'loose')
    fprintf(1, '\n');
end

end % Disp method

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Struct method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function s = struct(obj)
error('MATLAB:memmapfile:noStructConversion', ...
      'Memmapfile objects cannot be converted to structures.');
end % Struct method

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% horzcat method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function c=horzcat(varargin)
error(hGetCommonMemmapfileError('MATLAB:memmapfile:noCatenation'));
end % horzcat

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% vertcat method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function c=vertcat(varargin)
error(hGetCommonMemmapfileError('MATLAB:memmapfile:noCatenation'));
end % vertcat

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% cat method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function c=cat(varargin)
error(hGetCommonMemmapfileError('MATLAB:memmapfile:noCatenation'));
end % cat


end % Class definition

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Helper functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% -------------------------------------------------------------------------
% Validate memmapfile Format field setting.
function isvalid = hIsValidFormat(format)

if ischar(format)
    switch(format)
        case {'double', 'int8', 'int16', 'int32', 'int64', ...
              'uint8', 'uint16', 'uint32', 'uint64', ...
              'single'}
            isvalid = true;
        otherwise
            isvalid = false;
    end

elseif iscell(format)
    if size(format, 1) < 1 || size(format, 2) ~= 3
        isvalid = false;
    else
        isvalid = true;
        for i = 1:size(format, 1)
            field1 = format{i,1};
            field2 = format{i,2};
            field3 = format{i,3};
            % field 1 must be a string containing one of the supported
            % basic data types.
            if ~ischar(field1) || ~hIsValidFormat(field1)
                isvalid = false;
            % field 2 must be a 1xN double array such that N > 0. 
            elseif ~isa(field2, 'double') || ndims(field2) ~= 2 || ...
                    size(field2, 1) ~= 1 || size(field2, 2) < 1
                isvalid = false;
            % field 2 must contain nonnegative integral values 
            elseif any(field2 < 0 | ~isfinite(field2) | ...
                       ~isreal(field2) | field2 ~= fix(field2))
                isvalid = false;
            % field 3 must be a legal MATLAB variable name.
            elseif ~isvarname(field3)
                isvalid = false;
            end
        end
        
        % Make sure all field names are unique and that overall frame size is > 0
        if isvalid
            fields = format(:,3);
            if length(fields) > length(unique(fields))
                isvalid = false;
            elseif hFrameSize(format) == 0
                isvalid = false;
            end
        end
    end
else
    isvalid = false;
end

end % hIsValidFormat


% -------------------------------------------------------------------------
% Return size of a single frame in bytes.
function sz = hFrameSize(format)

sz = 0;
if iscell(format)
    for i=1:size(format, 1)
        sz = sz + hFrameSize(format{i,1}) * prod(format{i,2});
    end
else
    switch format
        case {'int8', 'uint8'}
            sz = 1;
            
        case {'int16', 'uint16'}
            sz = 2;
            
        case {'int32', 'uint32', 'single'}
            sz = 4;
            
        case {'double', 'int64', 'uint64'}
            sz = 8;
    end
end

end % hFrameSize

% -------------------------------------------------------------------------
% Compute whether accessing data with the specified format/offset/repeat would 
% lead to an unaligned data exception.
function bad = hIsUnaligned(format, offset, repeat)
bad = false;
if iscell(format)
    loc = offset;
    
    % If we can run through the full frame twice legally, we should be able
    % to run through it infinitely many times, by induction. If repeat is only 1,
    % we only need to get through one frame to be legal.
    for frame = 1:min(2, repeat)
        for elem = 1:size(format, 1)
            if mod(loc, hFrameSize(format{elem,1})) ~= 0
                bad = true;
                return;
            end
            loc = loc + hFrameSize(format(elem,:));
        end
    end
else
    if mod(offset, hFrameSize(format)) ~= 0
        bad = true;
        return;
    end
end

end % hIsUnaligned

% -------------------------------------------------------------------------
% Resolve a filename into a fullpath name. origFilename is the name specified by the user;
% foundFilename is the name found by fopen (which includes path information if found on 
% the MATLABPATH).
function resolvedName = hResolveFilename(origFilename, foundFilename)
[directory, basename, extension] = fileparts(origFilename);
if isempty(directory)
    % If we can avoid CD'ing around, lets do it. CD'ing can be slow.
    
    % File found either on path or in current directory. Need to decide which
    directoryOfFound = fileparts(foundFilename);
    if isempty(directoryOfFound)
        % Must have been found in current directory
        resolvedName = fullfile(pwd, origFilename);
    else
        resolvedName = foundFilename;
    end
else
    % Expand partial path name to full name
    ws = warning('off');
    try
        oldDir = cd(directory);
        resolvedName = fullfile(cd(oldDir), [basename extension]);
    catch
        warning(ws);
        rethrow(lasterror);
    end
    warning(ws);
end

end % hResolveFilename

% -------------------------------------------------------------------------

% A(:)=B or A.f(:)=B has the internal effect of replacing
% A with B (after appropriate error checking is made). This causes us 
% to lose the memory mapped data pointer. To work around this,
% we transform to A(:,:)=reshape(B, size(A, 1), []) or
% A.f(:,:)=reshape(B, size(A, 1), [])
function [S, RHS] = hMapToTwoColonIndices(LHS, S, RHS)
RHS = reshape(RHS, size(LHS, 1), []);
S = [S substruct('()', {':', ':'})];
end % hMapToTwoColonIndices

% -------------------------------------------------------------------------
% Check to see if we need to work around internal optimization of single colon
% index. If subscript-assigning a non-scalar to a single-colon indexed variable,
% we need to map to an equivalent double colon index.
function [S, RHS] = hFixSubscriptedColonAssignment(LHS, S, RHS)
if isequal(S(end).subs, {':'}) && ~isscalar(RHS) && ...
        prod(size(LHS)) == prod(size(RHS))
    [S, RHS] = hMapToTwoColonIndices(LHS, S(1:end-1), RHS);
end
end % hFixSubscriptedColonAssignment

% -------------------------------------------------------------------------
% Check maximum value of subscript values in each subscript position against
% actual size of LHS in corresponding dimension. As a special case, if
% only one subscript position is used (i.e. A(M)=N) then check maximum value of
% M against total number of elements in N.
function outOfRange = hSubsasgnIndexOutOfRange(LHS, indices)
for index = 1:length(indices)
    I = indices{index};
    % colon index can never be bigger than existing dimension.
    if ~isequal(I, ':')
        % Convert logical indices to numeric indices with FIND.
        if islogical(I)
            Imax = max([0 find(I)]);
        else
            Imax = max([0 I]);
        end
        
        if (length(indices) == 1 && Imax > prod(size(LHS))) || ...
                length(indices)  > 1 && Imax > size(LHS, index)
            outOfRange = true;
            return;
        end
    end
end
outOfRange = false;
end % hSubsasgnIndexOutOfRange

% -------------------------------------------------------------------------
% Check if an subscripted assignment operation is actually a subscripted delete
% operation (i.e. assigning [] to a piece of an array). 
function isDeletion = hSubsasgnIsSubscriptedDeletion(subs, RHS)
if isempty(RHS) && (isa(RHS, 'double') || isa(RHS, 'char'))
    % if any subscript is empty, then don't consider this subscripted assignment. It
    % is either legal (if subs only contains one element) or an illegal
    % operation that hSubsasgn and MATLAB's built-in indexing code will catch
    % ("Indexed empty matrix assignment is not allowed.")
    isDeletion = ~any(cellfun('isempty',subs));
else
    isDeletion = false;
end
end % hSubsasgnIsSubscriptedDeletion

% -------------------------------------------------------------------------
% Lookup and return error struct for common error messages
function out = hGetCommonMemmapfileError(id)
switch lower(id)
    case 'matlab:memmapfile:nocatenation'
        msg = 'Memmapfile objects cannot be concatenated.';

    case 'matlab:memmapfile:illegaldatafieldmodification'
        msg = sprintf(['When the Data field of a memmapfile object is a structure,\n' ... 
               'it may not be replaced by assignment.']);
        
    case 'matlab:memmapfile:datafieldsizefixed'
        msg = sprintf(['Cannot change the size of the Data field via subscripted ' ...
                       'assignment.\nThe size of the Data field is determined by ' ...
                       'the Repeat and Format fields.']);

    case 'matlab:memmapfile:datasubfieldsizefixed'
        msg = sprintf(['Cannot change the size of a subfield of the Data field via ' ...
                       'subscripted\nassignment. The sizes of subfields of the Data ' ...
                       'field are determined\nby the Format field.']);
        
    case 'matlab:memmapfile:illegalparenindex'
        msg = 'Memmapfile objects may not be subscripted using ().';

    case 'matlab:memmapfile:illegalbraceindex'
        msg = 'Memmapfile objects may not be subscripted using {}.';

end

out.identifier = id;
out.message = msg;
end % hGetCommonMemmapfileError

