function out = saveobj(obj)
%SAVEOBJ Save filter for serial port objects.
%
%    B = SAVEOBJ(OBJ) is called by SAVE when a serial port object is
%    saved to a .MAT file. The return value B is subsequently used by
%    SAVE to populate the .MAT file.  
%
%    SAVEOBJ will be separately invoked for each object to be saved.
% 

%    MP 3-14-00
%    Copyright 2000 The MathWorks, Inc. 
%    $Revision: 1.5 $  $Date: 2000/08/29 23:48:34 $

% Determine the number of objects being saved.
allJObjects = igetfield(obj, 'jobject');
numJObjects = length(allJObjects);

% ***************************************************************************
% A structure will be created which contains:
% x(i).jobject  = jobject
% x(i).props    = get(jobject) % Read-only properties removed (except Type).
% x(i).location = locations of the object.
%
% The location of the object is represented as a cell of structs. The 
% structures contain the following information.
%
%  Ex. y(1).Temp = 4; y(2).Temp = s1; s.UserData = y;    
%      s1's location field contains:
%           x.ObjectIndex = 1;             % s is stored in x(1)
%           x.Property    = 'UserData';
%           x.Index       = {'()', 2, '.', 'Temp', '()', 1};
%
% Every jobject will be replaced with the index of the appropriate jobject
% in the x structure.
% ***************************************************************************

% Create an empty structure which contains the necessary information to 
% recreate all the objects.
propInfo = struct('jobject', '', 'props', '', 'location', '');

% Create a cell which contains information on the object's location in 
% the propInfo structure.
jobject = cell(1, numJObjects);

% Loop through each object and save.
for i = 1:numJObjects
    currentObj = allJObjects(i);
    
    if isempty(propInfo(1).jobject)
    	index = 1;
    else
        index = length(propInfo) +1;
    end
    
    % Determine if the object has already been saved.
    if localIsSaved(currentObj)
        % Object has been saved. Determine which object currentObj is equal to.
        jobject{i} = localFindSavedObject(currentObj, propInfo);
    elseif (isvalid(currentObj))
		% If the object is valid get it's property values.
        propInfo(index).jobject = currentObj;
        propInfo(index).props   = localFindSettableProperties(currentObj);
   		
        % Indicate that the object has been saved.
		localIndicateSaved(currentObj);
		
        % Store the location in the jobject array.
        jobject{i} = index;
        
        % Parse the object to determine if the UserData or the Action
        % properties contain any instrument objects.
        propInfo = localParseUserDataAndActionProps(currentObj, propInfo);
	else
        % Invalid object - save the class type. 
        propInfo(index).jobject = currentObj;        
        propInfo(index).props   = class(java(currentObj));
        
        % Indicate that the object has been saved.
		localIndicateSaved(currentObj);
        
        % Store the location in the jobject arrays.
        jobject{i} = index;
    end
end
    
% Create the object that is going to be saved.
savedObj = obj;
savedObj = isetfield(savedObj, 'jobject', jobject);

% Remove the saved field - Can't use instrfind since invalid objects could
% have been modified..
localRemoveSavedFlag(propInfo);

% Store the table of objects in the store field - minus the UDD objects.
instrumentField = igetfield(obj, 'instrument');
propInfo = rmfield(propInfo, 'jobject');
instrumentField = isetfield(instrumentField, 'store', propInfo);
savedObj = isetfield(savedObj, 'instrument', instrumentField);

% Define output.
out = savedObj;

% ************************************************************************
% Indicate that the object has been saved.
function localIndicateSaved(currentObj)

% Add a temporary property called Saved that cannot be accessed through
% SET or GET.
p = schema.prop(currentObj, 'Saved', 'double');
p.AccessFlags.PublicGet = 'off';
p.AccessFlags.PublicSet = 'off';

% ************************************************************************
% Determine if object has been saved. Returns a logical 1 if saved,
% otherwise returns a logical 0.
function isSaved = localIsSaved(currentObj)

isSaved = ~isempty(currentObj.findprop('Saved'));

% ************************************************************************
% Remove the saved flag.
function localRemoveSavedFlag(propInfo)

% Can't use instrfind since invalid objects could have been modified.
for i = 1:length(propInfo)
    currentObj = propInfo(i).jobject;
	if ~isempty(currentObj)
		delete(currentObj.findprop('Saved'));
	end
end

% ****************************************************************************
% Find index of already saved object.
function equalIndex = localFindSavedObject(currentObj, propInfo)

% Initialize variables.
equalIndex = 1;
javaCurrentObj = java(currentObj);

% Loop through and find equivalent object.
for i = 1:length(propInfo)
	if equals(javaCurrentObj, java(propInfo(i).jobject)) == 1;
        equalIndex = i;
    	break
	end
end

% ****************************************************************************
% Save the settable properties only including the Type property.
function settable = localFindSettableProperties(currentObj)

% Determine the property values.
allProps = get(currentObj);
propNames = fieldnames(allProps);

% Find the settable properties.
settableArray = isSettable(currentObj);

% Remove the non-settable properties from property structure.
nonSettableProps = {propNames{~logical(settableArray)}};
nonSettableProps(find(strcmp('Type', nonSettableProps))) = [];
settable = rmfield(allProps, nonSettableProps);
settable.ConstructorArgs = currentObj.constructorargs;

% ****************************************************************************
% Parse the UserData and the Action properties to determine if the value
% contains an instrument object.
function propInfo = localParseUserDataAndActionProps(currentObj, propInfo)

% Initialize variables.
index = length(propInfo);
allProps = propInfo(index).props;

% Find the parseable properties - Action properties and UserData.
parseProps = localFindParseProps(allProps);

% Loop through each property and determine if the property value contains
% an instrument object.
for i = 1:length(parseProps)
    propValue = getfield(allProps, parseProps{i});
    
    switch class(propValue)
    case {'serial', 'gpib', 'visa'}
    	currentObj = igetfield(propValue, 'jobject');
        propInfo = localSaveArray(currentObj, propInfo, parseProps{i}, index, 1);
    case 'instrument'
        % An array of objects.
        currentArray = igetfield(propValue, 'jobject');
        for j = 1:length(currentArray)
	        propInfo = localSaveArray(currentArray(j), propInfo, parseProps{i}, index, j);        
        end
    case 'struct'
        % Loop through each field and determine if the field contains an instrument
        % object.
        propInfo = localSaveStruct(propInfo, parseProps{i}, index, propValue, {});
    case 'cell'
        % Loop through each element of the cell array and determine if the cell
        % array element contains an instrument object.
        propInfo = localSaveCell(propInfo, parseProps{i}, index, propValue, {});
	case 'char'
	case 'double'
	otherwise
		propInfo = localSaveObject(propValue, propInfo, parseProps{i}, index, {});
    end
end

% ****************************************************************************
% Save other objects.
function propInfo = localSaveObject(currentObj, propInfo, prop, objectIndex, index)

propInfo(end+1).jobject = [];
propInfo(end).props     = saveobj(currentObj);
savedIndex              = length(propInfo);

% Remove the instrument object and add to the location cell.
propInfo = localAddArrayLocation(propInfo, savedIndex, prop, objectIndex, index);

% ****************************************************************************
% Save an array of objects.
function propInfo = localSaveArray(currentObj, propInfo, prop, objectIndex, index)

% Determine if the object has already been saved.
if localIsSaved(currentObj)
    % Object has been saved. Determine which object currentObj is equal to.
    savedIndex = localFindSavedObject(currentObj, propInfo);

    % Remove the instrument object and add to the location cell.
    propInfo = localAddArrayLocation(propInfo, savedIndex, prop, objectIndex, index);
    propInfo = localRemoveArrayElement(propInfo, index, objectIndex, prop);
elseif (isvalid(currentObj))
	% If the object is valid get it's property values.
    propInfo(end+1).jobject = currentObj;
    propInfo(end).props     = localFindSettableProperties(currentObj);
    savedIndex              = length(propInfo);
	
    % Indicate that the object has been saved.
	localIndicateSaved(currentObj);

    % Remove the instrument object and add to the location cell.
    propInfo = localAddArrayLocation(propInfo, savedIndex, prop, objectIndex, index);
    propInfo = localRemoveArrayElement(propInfo, index, objectIndex, prop);

    % Parse the object to determine if the UserData or the Action
    % properties contain any instrument objects.
    propInfo = localParseUserDataAndActionProps(currentObj, propInfo);
else
    % Invalid object - save the class type. 
    propInfo(end+1).jobject = currentObj;        
    propInfo(end).props     = class(java(currentObj));
    savedIndex              = length(propInfo);
    
    % Indicate that the object has been saved.
	localIndicateSaved(currentObj);

    % Remove the instrument object and add to the location cell.
    propInfo = localAddArrayLocation(propInfo, savedIndex, prop, objectIndex, index);
    propInfo = localRemoveArrayElement(propInfo, index, objectIndex, prop);
end

% ****************************************************************************
% Save a structure which may or may not contain instrument objects.
function propInfo = localSaveStruct(propInfo, prop, objectIndex, propValue, index)

% Get the fields of the property Value.
names = fieldnames(propValue);

% Loop through each field value and determine if it is set to a cell, struct or
% an instrument object.
for j = 1:length(propValue)
    for i = 1:length(names)
		value = getfield(propValue(j), names{i});
		if isstruct(value)
            index = localUpdateIndex('struct', index, {'()' j, '.', names{i}});
            propInfo = localSaveStruct(propInfo, prop, objectIndex, getfield(propValue(j), names{i}), index);
            index = index(1:end-4);
        elseif iscell(value)
            index = localUpdateIndex('struct', index, {'()' j, '.', names{i}});
            propInfo = localSaveCell(propInfo, prop, objectIndex, getfield(propValue(j), names{i}), index);
            index = index(1:end-4);
        elseif isa(value, 'instrument')
            % Done - found instrument object. 
            index = localUpdateIndex('struct', index, {'()' j, '.', names{i}});
            currentObj = igetfield(value, 'jobject');
            if length(currentObj) == 1
            	propInfo = localSaveArray(currentObj, propInfo, prop, objectIndex, index);
            else
                for k = 1:length(currentObj)
                    index(end+1:end+2) = {'()', k};
            		propInfo = localSaveArray(currentObj(k), propInfo, prop, objectIndex, index);
                    index = index(1:end-2);
                end
            end
            index = index(1:end-4);
		end
	end
end

% ****************************************************************************
% Save a structure which may or may not contain instrument objects.
function propInfo = localSaveCell(propInfo, prop, objectIndex, propValue, index)


% Loop through each cell and determine if it is set to a cell, struct or
% an instrument object.
for j = 1:prod(size(propValue))
    value = propValue{j};
	if isstruct(value)
        index = localUpdateIndex('cell', index, {'{}', j});
        propInfo = localSaveStruct(propInfo, prop, objectIndex, value, index);
        index = index(1:end-2);
    elseif iscell(value)
        index = localUpdateIndex('cell', index, {'{}', j});
        propInfo = localSaveCell(propInfo, prop, objectIndex, value, index);
        index = index(1:end-2);
    elseif isa(value, 'instrument')
        % Done - found instrument object. 
        index = localUpdateIndex('cell', index, {'{}', j});
        currentObj = igetfield(value, 'jobject');
        if length(currentObj) == 1
        	propInfo = localSaveArray(currentObj, propInfo, prop, objectIndex, index);
        else
           	for k = 1:length(currentObj)
          	 	index(end+1:end+2) = {'()', k};
            	propInfo = localSaveArray(currentObj(k), propInfo, prop, objectIndex, index);
                index = index(1:end-2);
            end
        end
        index = index(1:end-2);
    end
end

% ****************************************************************************
% Update indices.
function index = localUpdateIndex(type, index, value);

% Can't use END on any empty matrix.

switch (type)
case 'struct'
    if (isempty(index))
        index(1:4) = value;
    else
        index(end+1:end+4) = value;
    end
case 'cell'
    if (isempty(index))
        index(1:2) = value;
    else
        index(end+1:end+2) = value;
    end
end

% ****************************************************************************
% Add an array location.
function propInfo = localAddArrayLocation(propInfo, savedIndex, prop, objectIndex, arrayIndex)

%  Ex. y(1).Temp = 4; y(2).Temp = s1; s.UserData = y;    
%      s1's location field contains:
%           x.ObjectIndex = 1;             % s is stored in x(1)
%           x.Property    = 'UserData';
%           x.Index       = {'()', 2, '.', 'Temp', '()', 1};

% Get the current location field value.
allLocations = propInfo(savedIndex).location;

% Create the structure to be stored.
temp.ObjectIndex = objectIndex;
temp.Property = prop;
temp.Index = arrayIndex;    

% Add the structure to the propInfo array - Can't use END on an empty value.
count = length(allLocations) + 1;
allLocations{count} = temp; 
propInfo(savedIndex).location = allLocations;

% ****************************************************************************
% Remove the UDD object from propInfo.
function propInfo = localRemoveArrayElement(propInfo, index, objectIndex, propName)

propValue = getfield(propInfo(objectIndex).props, propName);

if isnumeric(index) 
    % index points to an instrument object.
    propValue = '';
	propInfo(objectIndex).props = setfield(propInfo(objectIndex).props, propName, propValue);
    return;
end

if length(index) >= 2 & (isnumeric(index{end}) & strcmp(index{end-1}, '()'))
    index = index(1:end-2);
end

% Build up the string to access the UDD object.
stringToAccessUDD = '';
for i = 1:2:length(index)
    currentDelimiter = index{i};
    switch (currentDelimiter)
    case '()'
        stringToAccessUDD = [stringToAccessUDD '(' num2str(index{i+1}) ')'];
    case '{}'
        stringToAccessUDD = [stringToAccessUDD '{' num2str(index{i+1}) '}'];
    case '.'
        stringToAccessUDD = [stringToAccessUDD '.' index{i+1}];
    end
end

% Set the UDD object to empty.
eval(['propValue' stringToAccessUDD ' = '''';']);

% Update the propInfo structure to contain property value minus UDD object.
propInfo(objectIndex).props = setfield(propInfo(objectIndex).props, propName, propValue);

% ****************************************************************************
% Determine which properties need to be parsed including the Action properties
% and UserData.
function parseProps = localFindParseProps(allProps)

% Initialize variables.
count = 1;

% Get a list of all properties.
propNames = fieldnames(allProps);

% Find the properties which contain the string 'Action'.
for i = 1:length(propNames)
    if findstr('Action', propNames{i})
        parseProps{count} = propNames{i};
        count = count+1;
    end
end

% Add UserData.
parseProps{count} = 'UserData';

