classdef Database < matlab.mixin.SetGet
% Database Handle to a CAN database file.
%
%   This class provides an interface to Vector CAN database (DBC)
%   files. It contains properties and methods to obtain information stored 
%   in the database related to the definition of messages and signals.
%
%   See also VNT.

% Copyright 2008-2022 The MathWorks, Inc.

properties (SetAccess = 'private')
    % Name - The name of the database file.
    Name
    % Path - The full path to the database file.
    Path
    % UTF8_File - UTF-8 canonicalized DBC file
    UTF8_File
end

properties (SetAccess = 'private')
    % Nodes - Names of the network nodes defined in the database file.
    Nodes
    % NodeInfo - An array of node information structures for all
    %  nodes defined in the database.
    NodeInfo
    % Messages - Names of the messages defined in the database file.
    Messages
    % MessageInfo - An array of message information structures for all
    %  messages defined in the database.
    MessageInfo
    % Attributes - Attribute information for the database file.
    Attributes
    % AttributeInfo - An array of attribute information structures for all
    %  attributes defined for the database itself.
    AttributeInfo
end

properties
    % UserData - Storage for custom information.
    UserData
end

properties (Hidden, SetAccess = 'private')
    % DatabaseTypeJ1939 - Boolean value to tell if this database is defined
    % for J1939 networks.
    DatabaseTypeJ1939 = false;
    % J1939Info - An array of structures of J1939 parameter details derived
    % from the database.
    J1939Info
    % J1939PGNs - An array of J1939 parameter group numbers contained in
    % the database.
    J1939PGNs
    % HandleName - The name of the handle reference to the database
    %   file used in the Vector CANdb library.
    HandleName
    % LastModifiedDate - The datetime of the file when the object
    %   information was cached.
    LastModifiedDate
end

properties (Transient, Hidden, SetAccess = 'private')
    % Usable - Indicates if the object is properly constructed and
    %   linked to a valid database file.
    Usable = false;
end

methods
    
    function obj = Database(file)
    % Database Construct a handle to a database file.
        
        % Perform an argument count check.
        narginchk(1, 1);
        
        % Convert string inputs to character vectors.
        file = convertStringsToChars(file);
        
        % Validate the file input.
        [fileName, fileExt, fileFullPath] =  can.validateCANdbFile(file);
    
        % Use a MATLAB builtin to generate a random string to be used with
        % the CANdbLib SDK when opening the DBC file.
        handleName = tempname;
        [~, handleName] = fileparts(handleName);
        
        % Append a safe prefix on front of the random string and take only
        % a certain number of characters of the random string. The Vector
        % CANdbLib SDK only allows 30 character handle names.
        handleName = ['db' handleName(1:28)];
        
        % Open the database in the CANdbLib SDK.
        
        % mexVectorCANdb expects UTF-8 encoded text. Verify and/or canonicalize encoding
        dbc_file = fileFullPath;
        h = fopen(dbc_file, 'r');
        [~,~,~,charset] = fopen(h);
        fclose(h);
        if charset ~= "UTF-8"
            % According to the web, DBC does not specify the text encoding, and was
            % developed in the age of ASCII. That said, European locales have used
            % local code page encodings. Thus, here we match the behavior for A2L files:
            % From https://www.asam.net/standards/detail/mcd-2-mc/wiki/
            % If no encoding is specified in the a2l-file, then ISO-8859-1 (Latin-1) is assumed.
            % Otherwise, a2l-files use the Unicode Transformation Format at least on the level
            % of UTF-8. Higher levels (UTF-16 and UTF-32) can be used.
            if ~startsWith(charset, "UTF-")
                charset = "ISO-8859-1";
            end
            h = fopen(dbc_file, 'r', 'n', charset);
            data = fread(h, '*char');
            fclose(h);
            dbc_file = tempname;
            h = fopen(dbc_file, 'w');
            fwrite(h, data, '*char');
            fclose(h);
        end

        [status, response] = mexVectorCANdb('OpenDatabase', dbc_file, handleName);

        % Error on failure to open database.
        if ~status
            error(message('vnt:Database:UnableToOpenDatabase', fileName, fileExt, response));
        end
        
        % Set the object's properties.
        obj.Name = fileName;
        obj.Path = fileFullPath;
        obj.UTF8_File = dbc_file;
        obj.HandleName = response;
        
        % Set all the data into the object.
        obj.refreshCachedInfo();

        % Flag the object as usable given it is fully constructed.
        obj.Usable = true;
    end
    
    function out = valueTableText(obj, messageName, signalName, valueTableValue)
    % valueTableText Look up a value table text value for a signal.
    %
    %   OUT = valueTableText(DB, MESSAGENAME, SIGNALNAME, VALUETABLEVALUE) 
    %   returns the text value from the numeric value in the value table 
    %   for a given signal from a specified message.
    %
    %   Examples:
    %       value = valueTableText(db, 'MessageName', 'SignalName', valueTableValue)
    %
    %   See also VNT.
        
        % Check the argument count.
        narginchk(4, 4);

        % Convert string inputs to character vectors.
        [messageName, signalName]  = convertStringsToChars(messageName, signalName);

        % Validate the arguments.
        validateattributes(messageName, {'char'}, {'nonempty', 'row'}, 'valueTableText', 'MESSAGENAME');
        validateattributes(signalName, {'char'}, {'nonempty', 'row'}, 'valueTableText', 'SIGNALNAME');
        validateattributes(valueTableValue, {'numeric'}, ...
            {'nonempty', 'scalar', 'integer', 'finite', 'nonnan'}, ...
            'valueTableText', ...
            'VALUETABLEVALUE');

        % Get the signal information. Note that if the message is not found
        % in the database, that error will get thrown from the signalInfo
        % method itself.
        signalInfo = obj.signalInfo(messageName, signalName);
        if isempty(signalInfo)
            error(message('vnt:Database:SignalNotFound', signalName, messageName));
        end
        
        % Error if this signal does not have a value table.
        if isempty(signalInfo.ValueTable)
            error(message('vnt:Database:ValueTableNotFound', signalName, messageName));
        end
       
        % Look up the value table text.
        lookUp = valueTableValue == [signalInfo.ValueTable.Value];
        if ~any(lookUp)
            error(message('vnt:Database:ValueTableEntryNotFound', ...
                valueTableValue, ...
                signalName, ...
                messageName));
        end
        
        % Return the value table value.
        out = signalInfo.ValueTable(lookUp).Text;
    end
    
    function out = attributeInfo(obj, attributeType, attributeName, varargin)
    % attributeInfo Get information about an attribute.
    %
    %   OUT = attributeInfo(DB, 'Database', ATTRNAME) returns a structure 
    %   containing information for the specified database attribute.
    %
    %   OUT = attributeInfo(DB, 'Node', ATTRNAME, NODENAME) returns a structure 
    %   containing information for the specified node attribute.
    %
    %   OUT = attributeInfo(DB, 'Message', ATTRNAME, MESSAGENAME) returns a structure 
    %   containing information for the specified message attribute.
    %
    %   OUT = attributeInfo(DB, 'Signal', ATTRNAME, MESSAGENAME, SIGNALNAME) 
    %   returns a structure containing information for the specified signal attribute.
    %
    %   In all cases, if no matches are found in the database, an
    %   empty attribute information structure is returned.
    %
    %   The fields in the attribute information structure are:
    %       Name - The attribute's name.
    %       ObjectType - The type of attribute it is.
    %       DataType - The class of the attribute.
    %       DefaultValue - The default value assigned to the attribute.
    %       Value - The actual value of the attribute.
    %
    %   Examples:
    %       db = canDatabase('Database.dbc')
    %       attrInfo = attributeInfo(db, 'Database', 'AttrName')
    %       attrInfo = attributeInfo(db, 'Node', 'AttrName', 'NodeName')
    %       attrInfo = attributeInfo(db, 'Message', 'AttrName', 'MessageName')
    %       attrInfo = attributeInfo(db, 'Signal', 'AttrName', 'MessageName', 'SignalName')
    %
    %   See also VNT.
        
        % Check the argument count.
        narginchk(3, 5);
        
        % Convert string inputs to character vectors.
        [attributeType, attributeName, varargin{:}]  = convertStringsToChars(attributeType, attributeName, varargin{:});

        % Validate the arguments.
        validateattributes(attributeType, {'char'}, {'nonempty', 'row'}, 'attributeInfo', 'ATTRIBUTETYPE');
        validateattributes(attributeName, {'char'}, {'nonempty', 'row'}, 'attributeInfo', 'ATTRIBUTENAME');
        
        % Process based on the type of attribute being requested.
        switch attributeType
            case 'Database'
                % Get the specific attribute information requested.
                out = obj.AttributeInfo(strcmp(obj.Attributes, attributeName));
                
            case 'Node'
                % Validate the input.
                nodeName = varargin{1};
                validateattributes(nodeName, {'char'}, {'nonempty', 'row'}, 'attributeInfo', 'NODENAME');

                % Get the information structure for this type.
                infoStruct = obj.nodeInfo(nodeName);
                % Error if the specified item was not found.
                if isempty(infoStruct)
                    error(message('vnt:Database:NodeNotFound', nodeName));
                end
                % Get the specific attribute information requested.
                out = infoStruct.AttributeInfo(strcmp(infoStruct.Attributes, attributeName));
                
            case 'Message'
                % Validate the input.
                messageName = varargin{1};
                validateattributes(messageName, {'char'}, {'nonempty', 'row'}, 'attributeInfo', 'MESSAGENAME');
                
                % Get the information structure for this type.
                infoStruct = obj.messageInfo(messageName);
                % Error if the specified item was not found.
                if isempty(infoStruct)
                    error(message('vnt:Database:MessageNotFound', messageName));
                end
                % Get the specific attribute information requested.
                out = infoStruct.AttributeInfo(strcmp(infoStruct.Attributes, attributeName));
                
            case 'Signal'
                % Validate the input.
                messageName = varargin{1};
                signalName = varargin{2};
                validateattributes(messageName, {'char'}, {'nonempty', 'row'}, 'attributeInfo', 'MESSAGENAME');
                validateattributes(signalName, {'char'}, {'nonempty', 'row'}, 'attributeInfo', 'SIGNALNAME');
                
                % Get the information structure for this type.
                infoStruct = obj.signalInfo(messageName, signalName);
                % Error if the specified item was not found.
                if isempty(infoStruct)
                    error(message('vnt:Database:SignalNotFound', signalName, messageName));
                end
                % Get the specific attribute information requested.
                out = infoStruct.AttributeInfo(strcmp(infoStruct.Attributes, attributeName));
                
            otherwise
                % An invalid type was specified.
                error(message('vnt:Database:AttributeTypeError'));
        end
    end
    
    function out = nodeInfo(obj, varargin)
    % nodeInfo Get information about a node(s).
    %
    %   OUT = nodeInfo(DB) returns a structure containing information
    %   for all nodes found in the database DB.
    %
    %   OUT = nodeInfo(DB, NAME) returns a structure containing
    %   information for the node found in the database DB. NAME
    %   is the name of the node as a string.
    %
    %   In all cases, if no matches are found in the database, an
    %   empty node information structure is returned.
    %
    %   The fields in the node information structure are:
    %       Name - The node's name.
    %       Comment - A text comment about the node.
    %
    %   Examples:
    %       db = canDatabase('Database.dbc')
    %       nodeInfo = nodeInfo(db)
    %       nodeInfo = nodeInfo(db, 'NodeName')
    %
    %   See also VNT.
        
        % Check the argument count.
        narginchk(1, 2);
        
        % For 1 input argument, return all information structures.
        if nargin == 1
            % Return all of the structures.
            out = obj.NodeInfo;
            return;
        end
        
        % Convert string inputs to character vectors.
        [varargin{:}]  = convertStringsToChars(varargin{:});

        % When 2 input arguments are received, the information is being 
        % queried by name.
        nodeName = varargin{1};
        validateattributes(nodeName, {'char'}, {'nonempty', 'row'}, 'nodeInfo', 'NAME');
        
        % Find the requested item by name.
        out = obj.NodeInfo(strcmp(nodeName, obj.Nodes));
    end
    
    function out = messageInfo(obj, varargin)
    % messageInfo Get information about a message(s).
    %
    %   OUT = messageInfo(DB) returns a structure containing information
    %   for all messages found in the database DB.
    %
    %   OUT = messageInfo(DB, NAME) returns a structure containing
    %   information for the message found in the database DB. NAME
    %   is the name of the message as a string.
    %
    %   OUT = messageInfo(DB, ID, EXTENDED) returns a structure
    %   containing information for the message found in the database
    %   DB. ID is a numeric message identifier. EXTENDED is a
    %   logical value that is true for an extended (29 bit) ID and
    %   false for a standard (11 bit) ID.
    %
    %   In all cases, if no matches are found in the database, an
    %   empty message information structure is returned.
    %
    %   The fields in the message information structure are:
    %       Name - The message's name.
    %       Comment - A text comment about the message.
    %       ID - The message's numeric identifier.
    %       Extended - A logical value indicating if the identifier is
    %          standard or extended.
    %       Length - The message's data length.
    %       Signals - A list of signal names contained in the message.
    %
    %   Examples:
    %       db = canDatabase('Database.dbc')
    %       msgInfo = messageInfo(db)
    %       msgInfo = messageInfo(db, 'MsgName')
    %       msgInfo = messageInfo(db, 500, false)
    %
    %   See also VNT.
        
        % Check the argument count.
        narginchk(1, 3);
        
        % For 1 input argument, return all information structures.
        if nargin == 1
            % Return all of the structures.
            out = obj.MessageInfo;
            return;
        end
        
        % Convert string inputs to character vectors.
        [varargin{:}]  = convertStringsToChars(varargin{:});

        % When 2 input arguments are received, the information is being 
        % queried by name.
        if nargin == 2
            % Validate the arguments.
            messageName = varargin{1};
            validateattributes(messageName, {'char'}, {'nonempty', 'row'}, 'messageInfo', 'NAME');
            
            % Find the requested item by name.
            out = obj.privateMessageInfoByName(messageName);
            return;
        end
        
        % When 3 input arguments are received, the information is being
        % queried by ID/extended value.
        messageID = varargin{1};
        validateattributes(messageID, {'numeric'}, ...
            {'finite', 'integer', 'nonempty', 'nonnan', ...
            'nonnegative', 'nonsparse', 'real', 'scalar'}, ...
            'messageInfo', 'ID');
        messageIDType = varargin{2};
        validateattributes(messageIDType, {'logical'}, ...
            {'nonempty', 'scalar'}, ...
            'messageInfo', 'EXTENDED');
        
        % Find the item entry that matches both the ID and the ID type.
        out = obj.privateMessageInfoByID(messageID, messageIDType);
    end
    
    function out = signalInfo(obj, messageName, varargin)
    % signalInfo Get information about a signal(s).
    %
    %   OUT = signalInfo(DB, MSGNAME) returns a structure containing
    %   information for all signals contained in the message found
    %   in the database DB. MSGNAME is the name of the message as a
    %   string.
    %
    %   OUT = signalInfo(DB, ID, EXTENDED) returns a structure
    %   containing information for all signals contained in the
    %   message found in the database DB. ID is a numeric message
    %   identifier. EXTENDED is a logical value that is true for an
    %   extended (29 bit) ID and false for a standard (11 bit) ID.
    %
    %   OUT = signalInfo(DB, MSGNAME, SIGNALNAME) returns a structure
    %   containing information for the signal contained in the message
    %   found in the database DB. MSGNAME is as described above.
    %   SIGNALNAME is the name of the signal as a string.
    %
    %   OUT = signalInfo(DB, ID, EXTENDED, SIGNALNAME) returns a
    %   structure containing information for the signal contained
    %   in the message found in the database DB. ID, EXTENDED, and
    %   SIGNALNAME are as described above.
    %
    %   In all cases, if no matches are found in the database, an
    %   empty signal information structure is returned.
    %
    %   The fields in the signal information structure are:
    %       Name - The signal's name.
    %       StartBit - The starting bit number of the signal's location
    %           in the message data.
    %       SignalSize - The size in bits of the signal.
    %       ByteOrder - The byte ordering of the signal as
    %          'LittleEndian' or 'BigEndian'.
    %       Signed - A logical value indicating true for a signed signal
    %           or false for an unsigned signal.
    %       ValueType - A string indicating if the signal is an
    %           'Integer', 'Single', or 'Double' type.
    %       Class - The functional MATLAB class of the signal based on
    %           the SignalSize, ValueType, and Signed values.
    %       Factor - The physical scaling factor of the signal.
    %       Offset - The physical offset value of the signal.
    %       Minimum - The physical minimum value of the signal.
    %       Maximum - The physical maximum value of the signal.
    %       Units - The signal's physical engineering units.
    %       Comment - A text comment about the signal.
    %       Multiplexor - A logical value indicating true if this
    %           signal is a multiplexor, otherwise false.
    %       Multiplexed - A logical value indicating true if this
    %           signal is multiplexed, otherwise false.
    %       MultiplexMode - A numeric value indicating the value that
    %           the multiplexor must have for this signal to be active
    %           if this signal is multiplexed.
    %
    %   Examples:
    %       db = canDatabase('Database.dbc')
    %       sigInfo = signalInfo(db, 'MsgName')
    %       sigInfo = signalInfo(db, 500, false)
    %       sigInfo = signalInfo(db, 'MsgName', 'SignalName')
    %       sigInfo = signalInfo(db, 500, false, 'SignalName')
    %
    %   See also VNT.
        
        % Check the argument count.
        narginchk(2, 4);
        
        % Convert string inputs to character vectors.
        [messageName, varargin{:}]  = convertStringsToChars(messageName, varargin{:});
        
        % Validate the input based on how the message was specified. It
        % can be given as a string name or a numeric ID/extended value.
        if ischar(messageName)
            % Fully validate the message name.
            validateattributes(messageName, {'char'}, ...
                {'nonempty', 'row'}, 'signalInfo', 'MESSAGENAME');
            
            % Parse the signal name.
            if nargin == 3
                % Set the signal name if provided as input.
                signalName = varargin{1};
            else
                % Otherwise set as empty.
                signalName = [];
            end
            
            % Get the message information.
            messageInfo = obj.privateMessageInfoByName(messageName);
            
        elseif isnumeric(messageName)
            % Validate the existence of the extended value.
            if nargin < 3
                error(message('vnt:Database:MissingExtendedArgument'));
            end
            
            % Validate the extended flag.
            messageID = messageName;
            messageIDType = varargin{1};
            validateattributes(messageIDType, {'logical'}, ...
                {'nonempty', 'scalar'}, ...
                'signalInfo', 'EXTENDED');
            
            % Parse the signal name.
            if nargin == 4
                % Set the signal name if provided as input.
                signalName = varargin{2};
            else
                % Otherwise set as empty.
                signalName = [];
            end
            
            % Get the message information.
            messageInfo = obj.privateMessageInfoByID(messageID, messageIDType);

        else
            % The definition of the message information was invalid.
            error(message('vnt:Database:InvalidMessageArguments'));
        end
        
        % Error if the message information is not found.
        if isempty(messageInfo)
            error(message('vnt:Database:MessageNotFound', messageName));
        end
        
        % Return the information as requested.
        if isempty(signalName)
            % Return all of the information items.
            out = messageInfo.SignalInfo;
        else
            % Validate the signal name input.
            validateattributes(signalName, {'char'}, ...
                {'nonempty', 'row'}, 'signalInfo', 'SIGNALNAME');
            
            % Find and return just the one information structure.
            out = messageInfo.SignalInfo(strcmp(signalName,  messageInfo.Signals));
        end
    end
    
end


methods (Hidden)

    function out = privateMessageInfoByName(obj, messageName)
    % privateMessageInfoByName Internal query for message information by name.
    %
    %   This method is used internally to query the database file for
    %   message information. It is provided publically but hidden in
    %   order to provide expedited access to message information within 
    %   the product.
    
        % Get the information by name comparison and logical indexing.
        out = obj.MessageInfo(strcmp(messageName, obj.Messages));
    end
    
    function out = privateMessageInfoByID(obj, messageID, messageIDType)
    % privateMessageInfoByID Internal query for message information by ID.
    %
    %   This method is used internally to query the database file for
    %   message information. It is provided publically but hidden in
    %   order to provide expedited access to message information within 
    %   the product.

        % Get the information by logical index matching the ID and type.
        indexID = [obj.MessageInfo.ID] == messageID;
        indexIDType = [obj.MessageInfo.Extended] == messageIDType;
        out = obj.MessageInfo(indexID & indexIDType);
    end
    
    function out = privateMessageInfoByPGN(obj, messagePGN)
    % privateMessageInfoByPGN Internal query for J1939 message information by PGN.
    %
    %   This method is used internally to query the database file for
    %   J1939 message information. It is provided publically but hidden in
    %   order to provide expedited access to message information within 
    %   the product.

        % Get the information by logical index matching the PGN.
        indexPGN = obj.J1939PGNs == messagePGN;
        out = obj.MessageInfo(indexPGN);
    end

    
    function [status, text, signalValues] = getSignalValueAll(obj, msgName, data)
    % getSignalValueAll Unpacks all signal values using the CANdb interface.
    %
    %   This method is used internally to unpack signal values. It
    %   takes the name of the message and the data array from which
    %   to unpack as input. It returns status information and unpacked
    %   physical values for every signal contained in the message. The
    %   signal data is returned as a structure with a field for each
    %   signal value named after the signal. It uses the CANdb
    %   interface to perform the unpacking operation.
        
        [status, text, signalValues] = mexVectorCANdb( ...
            'GetSignalValueAll', ...
            obj.HandleName, ...
            msgName, ...
            data);
    end
    
    function [status, text, data] = setSignalPhysicalValue(obj, msgName, sigName, data, value)
    % setSignalPhysicalValue Packs a physical signal value using the CANdb interface.
    %
    %   This method is used internally to pack a signal value. It
    %   takes the name of the message and signal, the data array
    %   in which to pack, and value to pack as a physical value as input.
    %   It returns status information as well as the updated, packed
    %   data array. It uses the CANdb interface to perform the
    %   unpacking operation.
        
        [status, text, data] = mexVectorCANdb( ...
            'SetSignalPhysicalValue', ...
            obj.HandleName, ...
            msgName, ...
            sigName, ...
            data, ...
            value);
    end
    
end


methods (Hidden)
    
    function delete(obj)
    % delete Delete the database handle.
    %
    %   This method is private to discourage the user from explictly
    %   deleting a database. Normal reference counting will take care
    %   of deleting an object when all references are gone. Manual
    %   deletion will only invalidate an object. Users should use clear.
        
        % Close the reference to the database in the Vector CANdb
        % library only when the object is known good. This prevents any
        % undesirable closing of access to file on failed construction
        % or failed load of the object.
        if obj.Usable
            mexVectorCANdb('CloseDatabase', obj.HandleName);

            if ~strcmp(obj.Path, obj.UTF8_File)
                delete(obj.UTF8_File);
            end

        end
    end
    
    function refreshCachedInfo(obj)
    % refreshCachedInfo Update parsed file information in the object.
    %
    %   This method takes care of caching various file information from the 
    %   source file into the object itself.
    
        % Reopen the database in case the file was modified since
        % the original access. Reopening the file makes any changes
        % to the file available through the handle.
        [status, response] = mexVectorCANdb('ReopenDatabase', obj.HandleName);

        % Error on failure to reopen.
        if ~status
            error(message('vnt:Database:UnableToReopenDatabase', obj.Name, response));
        end
    
        % Store the datetime of the file so we know when details were read.
        fileInfo = dir(obj.Path);
        obj.LastModifiedDate = datetime(fileInfo.datenum, 'ConvertFrom', 'datenum');

        % Retrieve the node information for all nodes.
        obj.NodeInfo = mexVectorCANdb('GetNode', obj.HandleName);
        % Sort the node information structures alphabetically and set the
        % names field.
        [~, index] = sort({obj.NodeInfo.Name});
        obj.NodeInfo = obj.NodeInfo(index);
        obj.Nodes = {obj.NodeInfo.Name}';
        % Process the node attributes.
        for ii = 1:numel(obj.NodeInfo)
            % Sort the attribute information structures alphabetically and
            % set the names field.
            [~, index] = sort({obj.NodeInfo(ii).AttributeInfo.Name});
            obj.NodeInfo(ii).AttributeInfo = obj.NodeInfo(ii).AttributeInfo(index);
            obj.NodeInfo(ii).Attributes = {obj.NodeInfo(ii).AttributeInfo.Name}';
        end
        
        % Retrieve the message information for all messages.
        obj.MessageInfo = mexVectorCANdb('GetMessage', obj.HandleName);
        % Remove the Vector independent signal message if it exists.
        % This message is a catch all for any signals in the database
        % not packed into messages, but we do not want to give it to
        % the user nor show it in Simulink.
        index = strcmpi({obj.MessageInfo.Name}, 'VECTOR__INDEPENDENT_SIG_MSG');
        obj.MessageInfo(index) = [];
        % Sort the message information structures alphabetically and set the
        % names field.
        [~, index] = sort({obj.MessageInfo.Name});
        obj.MessageInfo = obj.MessageInfo(index);
        obj.Messages = {obj.MessageInfo.Name}';
        % Process the message attributes.
        for ii = 1:numel(obj.MessageInfo)
            % Sort the signal information structures alphabetically and
            % set the names field.
            [~, index] = sort({obj.MessageInfo(ii).SignalInfo.Name});
            obj.MessageInfo(ii).SignalInfo = obj.MessageInfo(ii).SignalInfo(index);
            obj.MessageInfo(ii).Signals = {obj.MessageInfo(ii).SignalInfo.Name}';
            % Sort the attribute information structures alphabetically and
            % set the names field for each signal information structure.
            for jj = 1:numel(obj.MessageInfo(ii).SignalInfo)
                [~, index] = sort({obj.MessageInfo(ii).SignalInfo(jj).AttributeInfo.Name});
                obj.MessageInfo(ii).SignalInfo(jj).AttributeInfo = ...
                    obj.MessageInfo(ii).SignalInfo(jj).AttributeInfo(index);
                obj.MessageInfo(ii).SignalInfo(jj).Attributes = ...
                    {obj.MessageInfo(ii).SignalInfo(jj).AttributeInfo.Name}';
            end
            
            % Sort the attribute information structures alphabetically and
            % set the names field.
            [~, index] = sort({obj.MessageInfo(ii).AttributeInfo.Name});
            obj.MessageInfo(ii).AttributeInfo = obj.MessageInfo(ii).AttributeInfo(index);
            obj.MessageInfo(ii).Attributes = {obj.MessageInfo(ii).AttributeInfo.Name}';
        end
        
        % Load the attribute information at the database level.
        obj.AttributeInfo = mexVectorCANdb('GetDatabaseAttributes', obj.HandleName);
        % Sort the attribute information structures alphabetically and
        % set the names field.
        [~, index] = sort({obj.AttributeInfo.Name});
        obj.AttributeInfo = obj.AttributeInfo(index);
        obj.Attributes = {obj.AttributeInfo.Name}';
        
        % Set properties specific to J1939.
        attrInfo = obj.attributeInfo('Database', 'ProtocolType');
        if ~isempty(attrInfo)
            if strcmpi(attrInfo.Value, 'J1939')
                % Set the database as a J1939 type.
                obj.DatabaseTypeJ1939 = true;

                % Cache the J1939 information.
                obj.J1939Info = [obj.MessageInfo.J1939];
                if ~isempty(obj.J1939Info)
                    obj.J1939PGNs = [obj.J1939Info.PGN];
                end
            end
        end
    end        
    
end

methods (Static, Hidden)
    
    function newObj = loadobj(obj)
    % loadobj Load a database object from memory.
        
        % Obtain a reference to the database file being loaded. As the
        % underlying link to the actual file is lost between save and
        % load, the object cannot be default loaded and returned for
        % use. It must be reconnected to the actual database file.
        try
            newObj = can.DatabaseManager.getInstance.find(obj.Path);
            newObj.UserData = obj.UserData;
            
        catch err %#ok<NASGU>
            % Wipe out the object content when we cannot reconnect to the file.
            obj.Name = '';
            obj.Path = '';
            obj.Nodes = {};
            obj.NodeInfo = struct([]);
            obj.Messages = {};
            obj.MessageInfo = struct([]);
            obj.Attributes = {};
            obj.AttributeInfo = struct([]);
            obj.UserData = [];
            newObj = obj;
        end
    end

end

end
