Code covered by the BSD License  

Highlights from
TCP/IP Communications in Matlab

TCP/IP Communications in Matlab

by

 

22 Jun 2009 (Updated )

Sends/receives TCP packets using Matlab's Java interface. Now handles matrices and cell arrays, etc.

jtcp(actionStr,varargin)
function [varargout] = jtcp(actionStr,varargin)
%
% jtcp.m--Uses Matlab's Java interface to handle Transmission Control
% Protocol (TCP) communications with another application, either on the
% same computer or a remote one. Sends and receives messages of type 'int8'
% only.
%
% Note that the conventional "client/server" terminology used for TCP is
% somewhat misleading; a "client" requests a connection and a "server"
% accepts (or rejects) it, but once the connection is established, both the
% client or server can either write or read data over it.
%
% JTCPOBJ = JTCP('REQUEST',HOST,PORT) represents a request from a client to
% the specified server to establish a TCP/IP connection on the specified
% port. HOST can be either a hostname (e.g., 'www.example.com' or
% 'localhost') or a string representation of an IP address (e.g.,
% '192.0.34.166'). Port is an integer port number between 1025 and 65535.
% The specified port must be open in the receiving machine's firewall.
%
% JTCPOBJ = JTCP('ACCEPT',PORT) accepts a request for a connection from a
% client.
%
% JTCP('WRITE',JTCPOBJ,MSSG) writes the specified message to the TCP/IP
% connection. MSSG must be of type 'int8' (use "int8(mssg)" to convert
% strings to int8, but "typecast(mssg,'int8')" to convert numerical values
% to int8--see examples, below).
%
% MSSG = JTCP('READ',JTCPOBJ) reads a message from the TCP/IP connection.
% By default, all available bytes are read.
%
% MSSG = JTCP('READ',JTCPOBJ,'MAXNUMBYTES',MAXNUMBYTES) limits the number
% of bytes read in to a maximum of MAXNUMBYTES.
%
% MSSG = JTCP('READ',JTCPOBJ,'NUMBYTES',NUMBYTES) specifies the number of
% bytes to be read in. Returns an empty string if the exact number of
% bytes is not read in.
%
% JTCPOBJ = JTCP('CLOSE',JTCPOBJ) closes the TCP/IP connection. This should
% be done on both client and server.
%
% The REQUEST and ACCEPT modes of jtcp.m accept a timeout argument,
% specified as a parameter/value pair. For example,
%   JTCP('REQUEST',HOST,PORT,'TIMEOUT',2000)
% attempts to make a TCP connection, but gives up after 2000 milliseconds.
% Default timeout is 1 second. 
%
% jtcp.m's normal algorithm is highly inefficient for transferring large
% amounts of data because it uses a separate function call for each byte it
% reads. Efficiency can be improved greatly by downloading and compiling
% Rodney Thomson's DataReader java class and setting jtcp.m's
% doUseHelperClass variable to "true" (see code, below). 
%
% Inspired by (and largely stolen from) Rodney Thomson's example code on
% the MathWorks' File Exchange.
%
% e.g., Send/receive characters:
%    server: jTcpObj = jtcp('accept',21566,'timeout',2000);
%    client: jTcpObj = jtcp('request','192.0.34.166',21566,'timeout',10000);
%    client: jtcp('write',jTcpObj,int8('Hello server'));
%    server: mssg = jtcp('read',jTcpObj); char(mssg)
%    server: jtcp('write',jTcpObj,int8('Hello client'));
%    client: mssg = jtcp('read',jTcpObj); char(mssg)
%    server: jtcp('close',jTcpObj);
%    client: jtcp('close',jTcpObj);
%
% e.g., Send/receive numerical values:
%    client: jtcp('write',jTcpObj,typecast([1 2 333],'int8'));
%    server: mssg = jtcp('read',jTcpObj); typecast(mssg,'double')
%
% e.g., Send/receive matrix: must convert to vector before sending.
%    client: Z=get(0,'DefaultFigureColormap'); [numRows,numCols]=size(Z); 
%            Z=reshape(Z,1,numRows*numCols);
%            jtcp('write',jTcpObj,typecast(Z,'int8'));
%    server: mssg = jtcp('read',jTcpObj); 
%            reshape(typecast(mssg,'double'),64,3)

% Developed in Matlab 7.8.0.347 (R2009a) on GLNX86.
% Kevin Bartlett (kpb@uvic.ca), 2009-06-17 13:03
%-------------------------------------------------------------------------

% Improve reading efficiency by downloading and compiling Rodney Thomson's
% DataReader java class. Instruct jtcp.m to use DataReader by changing the
% doUseHelperClass variable (below) to "true" and editing the
% HELPER_CLASS_PATH to point the location of the compiled class.
%
% DataReader class available at:
% http://www.mathworks.com/matlabcentral/fileexchange/25249-tcpip-socket-communications-in-matlab-using-java-classes
doUseHelperClass = true;
HELPER_CLASS_PATH = '/home/bartlett/matlab/network/';
%HELPER_CLASS_PATH = '/home/bartlett/matlab/MFileExportDir/home/bartlett/matlab/network';

% Handle input arguments.
REQUEST = 1;
ACCEPT = 2;
WRITE = 3;
READ = 4;
CLOSE = 5;

remainingArgs = {};

if strcmpi(actionStr,'request')
    % JTCPOBJ = JTCP('REQUEST',HOST,PORT)
    action = REQUEST;
    
    if nargin < 3
        error([mfilename '.m--REQUEST mode requires at least 3 input arguments.']);
    end % if
    
    host = varargin{1};
    port = varargin{2};
    argNames = {'host' 'port'};
    
    if nargin > 3
        remainingArgs = varargin(3:end);
    end % if
    
elseif strcmpi(actionStr,'accept')
    % JTCPOBJ = JTCP('ACCEPT',PORT)
    action = ACCEPT;
    
    if nargin < 2
        error([mfilename '.m--ACCEPT mode requires at least 2 input arguments.']);
    end % if
    
    port = varargin{1};
    argNames = {'port'};
    
    if nargin > 2
        remainingArgs = varargin(2:end);
    end % if
    
elseif strcmpi(actionStr,'write')
    % NUMBYTES = JTCP('WRITE',JTCPOBJ,MSSG)
    action = WRITE;
    
    if nargin < 3
        error([mfilename '.m--WRITE mode requires at least 3 input arguments.']);
    end % if
    
    jTcpObj = varargin{1};
    mssg = varargin{2};
    argNames = {'jTcpObj' 'mssg'};
    
    if nargin > 3
        remainingArgs = varargin(3:end);
    end % if
    
elseif strcmpi(actionStr,'read')
    % MSSG = JTCP('READ',JTCPOBJ)
    % MSSG = JTCP('READ',JTCPOBJ,'MAXNUMBYTES',MAXNUMBYTES)
    % MSSG = JTCP('READ',JTCPOBJ,'NUMBYTES',NUMBYTES)
    action = READ;
    
    if nargin < 2
        error([mfilename '.m--READ mode requires at least 2 input arguments.']);
    end % if
    
    jTcpObj = varargin{1};
    argNames = {'jTcpObj'};
    
    if nargin > 2
        remainingArgs = varargin(2:end);
    end % if
    
elseif strcmpi(actionStr,'close')
    % JTCP('CLOSE',JTCPOBJ)
    action = CLOSE;
    
    if nargin < 2
        error([mfilename '.m--CLOSE mode requires at least 2 input arguments.']);
    end % if
    
    jTcpObj = varargin{1};
    argNames = {'jTcpObj'};
    
    if nargin > 1
        remainingArgs = varargin(2:end);
    end % if
    
else
    error([mfilename '.m--Unrecognised actionStr ''' actionStr ''.']);
end % if

% Parse remaining arguments.
if isempty(remainingArgs)
    maxNumBytes = +Inf;
    numBytes = NaN;
    timeout = 1000;
else
    defaultStruct.maxNumBytes = +Inf;
    defaultStruct.numBytes = NaN;
    defaultStruct.timeout = 1000;
    
    allowNewFields = 0;
    isCaseSensitive = 0;
    [argStruct,overRidden] = parse_args(defaultStruct,remainingArgs{:},allowNewFields,isCaseSensitive);
    maxNumBytes = argStruct.maxNumBytes;
    numBytes = argStruct.numBytes;
    timeout = argStruct.timeout;
end % if

% ...Check for validity of input arguments.
if maxNumBytes < 1
    error([mfilename '.m--Input argument ''maxNumBytes'' must be positive.']);
elseif isfinite(maxNumBytes)
    if rem(maxNumBytes,1)~=0
        error([mfilename '.m--Input argument ''maxNumBytes'' must be an integer.']);
    end % if
end % if

if numBytes < 1
    error([mfilename '.m--Input argument ''numBytes'' must be positive.']);
elseif ~isnan(numBytes)
    if rem(numBytes,1)~=0
        error([mfilename '.m--Input argument ''numBytes'' must be an integer.']);
    end % if
end % if

if timeout < 0
    error([mfilename '.m--Input argument ''timeout'' must be greater than zero.']);
end % if

if ismember('host',argNames)
    if ~ischar(host)
        error([mfilename '.m--Host name/IP must be a string (e.g., ''www.examplecom'' or ''208.77.188.166''.).']);
    end % if
end % if

if ismember('port',argNames)
    if ~isnumeric(port) | rem(port,1)~=0 | port < 1025 | port > 65535
       error([mfilename '.m--Port number must be an integer between 1025 and 65535.']);
    end % if
end % if

if ismember('jTcpObj',argNames)
    
    if ~isstruct(jTcpObj)
        error([mfilename '.m--Input argument ''jTcpObj'' must be a structure.']);
    end % if
    
    if ~isfield(jTcpObj,'socket')
        error([mfilename '.m--Input argument ''jTcpObj'' not of recognised format.']);        
    end % if
end % if

if ismember('mssg',argNames)
    if ~isa(mssg,'int8')
        error([mfilename '.m--Input argument ''mssg'' must be of type ''int8''.']);
    end % if
end % if

% Perform specified action.
if action == REQUEST
    jTcpObj = jtcp_request_connection(host,port,timeout);        
elseif action == ACCEPT
    jTcpObj = jtcp_accept_connection(port,timeout);
elseif action == WRITE
    jtcp_write(jTcpObj,mssg);
elseif action == READ
    mssg = jtcp_read(jTcpObj,maxNumBytes,numBytes,doUseHelperClass,HELPER_CLASS_PATH); 
elseif action == CLOSE
    jTcpObj = jtcp_close(jTcpObj);
end % if

if nargout > 0
    
    if ismember(action,[REQUEST ACCEPT CLOSE])
        varargout{1} = jTcpObj;
    elseif action == WRITE
        varargout{1} = [];
    elseif action == READ
        varargout{1} = mssg;
    elseif action == CLOSE
        varargout{1} = [];
    end % if
    
end % if

%-------------------------------------------------------------------------
function jTcpObj = jtcp_request_connection(host,port,timeout)
%
% jtcp_request_connection.m--Request a TCP connection from server.
%
% Syntax: jTcpObj = jtcp_request_connection(host,port,timeout)
%
% e.g., jTcpObj = jtcp_request_connection('208.77.188.166',21566,1000)

% Developed in Matlab 7.8.0.347 (R2009a) on GLNX86.
% Kevin Bartlett (kpb@uvic.ca), 2009-06-17 13:03
%-------------------------------------------------------------------------

import java.net.Socket
import java.io.*
import java.net.InetSocketAddress 
             
% Assemble socket address.
socketAddress = InetSocketAddress(host,port); 

% 2009-10-05--Suggestion from Derek Eggiman to clean up code. Instead of a
% while loop for the timeout, create an unconnected socket, then connect it
% to the host/port address while specifying a timeout.

% Establish unconnected socket. 
socket = Socket();

% Connect socket to address.
try 
    socket.connect(socketAddress,timeout); 
catch
    errorStr = sprintf('%s.m--Failed to make TCP connection.\nJava error message follows:\n%s',mfilename,lasterr);
    error(errorStr);
end % try

inputStream  = socket.getInputStream;
dataInputStream = DataInputStream(inputStream);
outputStream   = socket.getOutputStream;
dataOutputStream = DataOutputStream(outputStream);

jTcpObj.socket = socket;
jTcpObj.remoteHost = host;
jTcpObj.port = port;
jTcpObj.inputStream = inputStream;
jTcpObj.dataInputStream = dataInputStream;
jTcpObj.outputStream = outputStream;
jTcpObj.dataOutputStream = dataOutputStream;

%-------------------------------------------------------------------------
function jTcpObj = jtcp_accept_connection(port,timeout)
%
% jtcp_accept_connection.m--Accept a TCP connection from client.
%
% Syntax: jTcpObj = jtcp_accept_connection(port,timeout)
%
% e.g., jTcpObj = jtcp_accept_connection(21566,1000)

% Developed in Matlab 7.8.0.347 (R2009a) on GLNX86.
% Kevin Bartlett (kpb@uvic.ca), 2009-06-17 13:03
%-------------------------------------------------------------------------

import java.net.Socket
import java.io.*
import java.net.ServerSocket

serverSocket = ServerSocket(port);
serverSocket.setSoTimeout(timeout);

try
    socket = serverSocket.accept;
    outputStream   = socket.getOutputStream;
    dataOutputStream = DataOutputStream(outputStream);
    inputStream  = socket.getInputStream;
    dataInputStream = DataInputStream(inputStream);
    jTcpObj.socket = socket;
    inetAddress = socket.getInetAddress;
    host = char(inetAddress.getHostAddress);
    jTcpObj.remoteHost = host;
    jTcpObj.port = port;
    jTcpObj.outputStream = outputStream;
    jTcpObj.dataOutputStream = dataOutputStream;
    jTcpObj.inputStream = inputStream;
    jTcpObj.dataInputStream = dataInputStream;
catch ME
    serverSocket.close;
    rethrow(ME);
end % try

serverSocket.close;

%-------------------------------------------------------------------------
function [] = jtcp_write(jTcpObj,mssg)
%
% jtcp_write.m--Writes the specified message to the TCP/IP connection.
%
% Syntax: jtcp_write(jTcpObj,mssg)
%
% e.g.,   jtcp_write(jTcpObj,'howdy')

% Developed in Matlab 7.8.0.347 (R2009a) on GLNX86.
% Kevin Bartlett (kpb@uvic.ca), 2009-06-17 13:03
%-------------------------------------------------------------------------

% Matthias Geier's suggestion: change from writeBytes() to write():
%jTcpObj.dataOutputStream.writeBytes(char(mssg));
jTcpObj.dataOutputStream.write(mssg,0,length(mssg));
jTcpObj.dataOutputStream.flush;

%-------------------------------------------------------------------------
function [mssg] = jtcp_read(jTcpObj,maxNumBytes,numBytes,doUseHelperClass,HELPER_CLASS_PATH)
%
% jtcp_read.m--Reads the specified message from the TCP/IP connection.
%
% maxNumBytes and numBytes are ignored if specified as NaNs.
%
% Syntax: mssg = jtcp_read(jTcpObj,maxNumBytes,numBytes,doUseHelperClass,HELPER_CLASS_PATH);
%
% e.g.,   mssg = jtcp_read(jTcpObj,NaN,NaN,true,'/home/bartlett/javaclasses');

% Developed in Matlab 7.8.0.347 (R2009a) on GLNX86.
% Kevin Bartlett (kpb@uvic.ca), 2009-06-17 13:03
%-------------------------------------------------------------------------

numBytesAvailable = jTcpObj.inputStream.available;

% Default behaviour is to read in all available bytes.
numBytesToRead = numBytesAvailable;

% If a maximum number of bytes to read has been specified, and if that
% maximum number is less than the number of available bytes, then
% limit the number of bytes read in.
if maxNumBytes <= numBytesAvailable
    numBytesToRead = maxNumBytes;
end % if

% If an exact number of bytes to read in has been specified, then it
% trumps any value of maxNumBytes. 
if ~isnan(numBytes)
    numBytesToRead = numBytes;
end % if

% If the number of bytes to read exceeds the number available, then do
% not attempt to read; return an empty string.
if numBytesToRead > numBytesAvailable
    mssg = '';
else
           
    if doUseHelperClass == true
        % Use of the helper class has been specified, but the class has to
        % be on the java class path to be useable.
        dynamicJavaClassPath = javaclasspath('-dynamic');
        
        % Add the helper class path if it isn't already there.
        if ~ismember(HELPER_CLASS_PATH,dynamicJavaClassPath)
            javaaddpath(HELPER_CLASS_PATH);
            
            % javaaddpath issues a warning rather than an error if it fails, so
            % can't use try/catch here. Test again to see if helper path added.
            dynamicJavaClassPath = javaclasspath('-dynamic');
            
            if ~ismember(HELPER_CLASS_PATH,dynamicJavaClassPath)
                warning([mfilename '.m--Unable to add Java helper class; reverting to byte-by-byte (slow) algorithm.']);
                doUseHelperClass = false;
            end % if
            
        end % if
        
    end % if    
    
    % Read the message.
    if doUseHelperClass
        % Read incoming message using efficient single function call.
        data_reader = DataReader(jTcpObj.dataInputStream);
        mssg = data_reader.readBuffer(numBytesToRead);
        mssg = mssg(:)';
    else
        % Read incoming message byte-by-byte with separate function call
        % for each byte.
        mssg = zeros(1, numBytesToRead, 'int8');
        
        for i = 1:numBytesToRead,
            mssg(i) = jTcpObj.dataInputStream.readByte;
        end % for
        
    end % if
    
end % if

%-------------------------------------------------------------------------
function [jTcpObj] = jtcp_close(jTcpObj)
%
% jtcp_close.m--Closes the specified TCP/IP connection.
%
% Syntax: jTcpObj = jtcp_close(jTcpObj);
%
% e.g.,   jTcpObj = jtcp_close(jTcpObj);

% Developed in Matlab 7.8.0.347 (R2009a) on GLNX86.
% Kevin Bartlett (kpb@uvic.ca), 2009-06-17 13:03
%-------------------------------------------------------------------------

jTcpObj.socket.close;

%-------------------------------------------------------------------------
function [argStruct,varargout] = parse_args(varargin)
%
% parse_args.m--Parses input arguments for a client function. Arguments are
% assumed to be in parameter/value pair form. 
%
% The following example shows the steps involved in using parse_args.m:
%
% (1) Create m-file "myfunc.m" with the following function declaration:
%        function [] = myfunc(varargin)
%
% (2) Inside myfunc.m, include the following lines:
%        defaultVals.figureWidth = 0.8; 
%        defaultVals.figureHeight = 0.5;
%        argStruct = parse_args(defaultVals,varargin{:});
%
% (3) Call your function with the following syntax:
%        myfunc('figureHeight',0.99)
%
% Inside myfunc.m, the structured variable argStruct will contain fields
% named 'figureWidth' and 'figureHeight'. The 'figureWidth' field will
% contain the default value of 0.8, since no non-default value was passed
% to myfunc.m, but the 'figureHeight' field will contain the value 0.99,
% over-riding the default value of 0.5.
%
% There are variations on this pattern:
%
% (a) Your "myfunc.m" program may take arguments in addition to varargin,
%     but they must precede varargin. For example, your function
%     declaration could look like this:
%         function [] = myfunc(numFiles,pathName,varargin)
%
% (b) It is not necessary to declare any default values. Your "myfunc.m"
%     program can call parse_args.m like this:
%         argStruct = parse_args(varargin{:});
%
% (c) You can call myfunc.m with all parameter/value pairs specified, none
%     of them specified, or anything in between. In the example above,
%     myfunc.m could be called like this:
%         myfunc('figureHeight',0.99,'figureWidth',0.2);
%     or like this:
%         myfunc;
%
% (d) Your "myfunc.m" program  can call parse_args.m with two additional
%     input arguments:
%         argStruct = parse_args(...,allowNewFields,isCaseSensitive);
%     where allowNewFields and isCaseSensitive are both Boolean values. By
%     default, isCaseSensitive is true, so parse_args.m will treat the
%     parameters 'lineColour' and 'linecolour' (for example) as different
%     parameters. The allowNewFields parameter is also true by default,
%     meaning that parse_args.m will accept parameters whose names are NOT
%     included as fields in the default values structured variable. You can
%     over-ride the default behaviour by specifying a different value for
%     allowNewFields or isCaseSensitive, but in this case, BOTH of these
%     arguments must be specified in the call to parse_args.m as shown
%     above.
%
% (e) Your program may call parse_args.m with a second output argument:
%         [argStruct,overRidden] = parse_args(...
%     The "overRidden" output argument contains a list of those variables
%     (if any) whose default values were overridden by varargin elements.
%
% N.B., program originally named parvalpairs.m. Program is based on the
% (University of Hawaii) Firing Group's fillstruct.m.
%
% Syntax: [argStruct,<overRidden>] = parse_args(<defaultStruct>,...
%                                    par1,val1,par2,val2,...,
%                                    <allowNewFields,isCaseSensitive>)
%
% e.g.,   argStruct = parse_args('Position',[256 308 512 384],'Units','pixels','Color',[1 0 1])
%
% e.g.,   defaultStruct.a = pi; 
%         defaultStruct.b = 'hello'; 
%         defaultStruct.c = [1;2;3];
%         allowNewFields = 1; 
%         isCaseSensitive = 0; 
%         [argStruct,overRidden] = parse_args(defaultStruct,varargin{:},allowNewFields,isCaseSensitive);
%         % N.B., for demonstration on command line (where you won't have a
%         varargin variable), use this syntax: 
%         [argStruct,overRidden] = parse_args(defaultStruct,'A',pi/2,'b','bye','z','new field',allowNewFields,isCaseSensitive)

% Developed in Matlab 6.1.0.450 (R12.1) on Linux. Kevin
% Bartlett(kpb@hawaii.edu), 2003/04/08, 11:49
%--------------------------------------------------------------------------

% 2009-06-17--Mathworks now supplies the inputParser class to handle input
% arguments. Should probably migrate from parse_args to the "official"
% program.

% Handle input arguments.
args = varargin;

if nargin == 0
    argStruct = struct([]);
    overRidden = {};
    
    if nargout > 1
        varargout{1} = overRidden;
    end % if

    return;
end % if

% If a structured variable containing default field values has been
% supplied, separate it from the other input arguments.
defaultStruct = struct([]);

if isstruct(args{1})

    defaultStruct = args{1};

    if length(args) > 1
        args = args(2:end);
    else
        args = {};
    end % if

end % if

argStruct = defaultStruct;

% Determine if values of the variables "allowNewFields" and
% "isCaseSensitive" have been specified.

% ...Default values:
allowNewFields = 1;
isCaseSensitive = 1;

if length(args) > 1
    
    % If no values of allowNewFields and isCaseSensitive have been
    % specified, then all the remaining arguments will be parameter/value
    % pairs. The second-to-last argument should then be a string.
    if ~ischar(args{end-1})
    
        % The second-to-last argument is NOT a string, so it must be the
        % Boolean variable allowNewFields.
        allowNewFields = args{end-1};
        
        % ...and the last input argument is the Boolean variable
        % isCaseSensitive.
        isCaseSensitive = args{end};
        
        if length(args) > 1
           args = args(1:end-2);
        else
            args = {};
        end % if
        
    end % if
        
end % if

% If no arguments remain after extracting the default field values and the
% Boolean variables "allowNewFields" and "isCaseSensitive", then exit now.
% The value of argStruct returned will contain the same values as
% defaultStruct.
if isempty(args)
    overRidden = {};
    
    if nargout > 1
        varargout{1} = overRidden;
    end % if

    return;
end % if

if ~ismember(allowNewFields,[1 0])
    error([mfilename '.m--Value for "allowNewFields" must be 1 or 0.']);
end % if

if ~ismember(isCaseSensitive,[1 0])
    error([mfilename '.m--Value for "isCaseSensitive" must be 1 or 0.']);
end % if

% Remaining input arguments should be parameter/value pairs.
existingFieldNames = fieldnames(defaultStruct);
lowerExistingFieldNames = lower(existingFieldNames);
overRidden = cell(1,length(args)/2);

for iArg = 1:2:length(args)

    thisFieldName = args{iArg};
    overRidden{1+(iArg-1)/2} = thisFieldName;

    if ~ischar(thisFieldName)
        error([mfilename '.m--Parameter names must be strings.']);
    end % if

    thisField = args{iArg+1};

    % Find out if field already exists.
    if isCaseSensitive == 1

        if ismember(thisFieldName,existingFieldNames)
            fieldExists = 1;
            fieldNameToInsert = thisFieldName;
        else
            fieldExists = 0;
            fieldNameToInsert = thisFieldName;
        end % if

    else

        if ismember(lower(thisFieldName),lowerExistingFieldNames)
            fieldExists = 1;
            matchIndex = strmatch(lower(thisFieldName),lowerExistingFieldNames,'exact');
            fieldNameToInsert = existingFieldNames{matchIndex};
        else
            fieldExists = 0;
            fieldNameToInsert = thisFieldName;
        end % if

    end % if

    % If new fields are not permitted to be added, test that field already exists.
    if allowNewFields == 0 && fieldExists == 0
        
        % If the default structure is empty, and the user is not permitting
        % the addition of new fields, there is no point in running this
        % program; probably the user doesn't intend this.
        if isempty(defaultStruct)
            error([mfilename '.m--Need to permit the addition of new fields if no default structure specified.']);
        end % if

        if isCaseSensitive == 1
            error([mfilename '.m--Unrecognised input argument ''' thisFieldName '''. (arguments are case-sensitive).']);
        else
            error([mfilename '.m--Unrecognised input argument ''' thisFieldName '''.']);
        end % if

    end % if

    if isempty(argStruct)
        argStruct = struct(fieldNameToInsert,thisField);
    else
        argStruct.(fieldNameToInsert) = thisField;
    end % if
            
end % for

if nargout > 1
    varargout{1} = overRidden;
end % if

Contact us