classdef Inspector < handle
%CODERDI.INSPECTOR provides a way to inspect intermediate CoderDI results.
%
% CODERDI.INSPECTOR is a client utility that interprets the CoderDI dump
% from a model's code generation process. A typical workflow of using
% this utility is demonstrated below:
%
% step 0. Enable feature:
% >> slfeature('EnableCoderDIMatlabAPI',1);
%
% step 1. Generate code:
% >> rtwbuild(mdl);
%
% step 2. Initialize the inspector:
% >> insp = coderdi.Inspector('build/directory/CoderDI.xml');
%
% step 3. Get CoderDI for a modeling element (by search):
% >> di = insp.getCoderDIFor...(...);
%
% Getting CoderDI for different types of modeling elements:
% >> riDi = insp.getCoderDIForRootInport('.*In1.*'); % Get the CoderDI of a root inport by searching its block name, signal label, and mapping identifier.
% >> roDi = insp.getCoderDIForRootOutport('.*Out1.*'); % Get the CoderDI of a root outport by searching its block name, signal label, and mapping identifier.
% >> boDi = insp.getCoderDIForBlockOutput('.*gain1.*', 0); % Get the CoderDI of a block output by searching its block name, signal label, and mapping identifier and by the outport index.
% >> stDi = insp.getCoderDIForState('.*mydelay.*', 0); % Get the CoderDI of a state by searching its block name, state name, and mapping identifier and by the dwork index.
% >> dsmDi = insp.getCoderDIForDataStore('.*myDSM.*'); % Get the CoderDI of a data store by searching its block name, data store name, and mapping identifier.
% >> rtansitionDi = insp.getCoderDIForSpecialBlock('.*Rate Trans.*'); % Get the CoderDI of a special block based CoderDI owning modeling element by searching its block name.
% >> prmDi = insp.getCoderDIForParameter('.*myparam1.*'); % Get the CoderDI of a parameter by searching its name and mapping identifier.
%
% step 4. Inspect the CoderDI
% >> di.getStorageClass();
%
% To get help of CoderDI APIs
% >> help coderdi.CoderDI

% Copyright 2020-2021 The MathWorks, Inc.

properties (Access = private)
mDataModel;
end

methods (Access = public)

function this = Inspector(fileOrModel)
% Instantiate the inspector with the path to a CoderDIDump or a loaded mf0 model object.
if ischar(fileOrModel)
this.mDataModel = mf.zero.Model;
this.load(fileOrModel);
elseif isa(fileOrModel, 'mf.zero.Model')
this.mDataModel = fileOrModel;
end
end

function save(this, filename)
% Save the CoderDI dump to a different file.
serializer = mf.zero.io.XmlSerializer;
serializer.serializeToFile(this.mDataModel, filename);
end

function load(this, filename)
% Load CoderDI dump from a given file.
parser = mf.zero.io.XmlParser;
parser.RemapUuids = true;
parser.Model = this.mDataModel;

transaction = this.mDataModel.beginTransaction;
topElems = this.mDataModel.topLevelElements;
parser.parseFile(filename);
topElems.destroy;
transaction.commit;
end

function di = getCoderDIForRootOutport(this, namepattern)
% Get the CoderDI of a root inport by searching its block name, signal label, and mapping identifier.
di = getCoderDI(this, checkSearchUniqueness(this, getRootOutports(this, namepattern)));
end

function di = getCoderDIForRootInport(this, namepattern)
% Get the CoderDI of a root outport by searching its block name, signal label, and mapping identifier.
di = getCoderDI(this, checkSearchUniqueness(this, getRootInports(this, namepattern)));
end

function di = getCoderDIForSpecialBlock(this, namepattern)
% Get the CoderDI of a special block based CoderDI owning modeling element by searching its block name.
di = getCoderDI(this, checkSearchUniqueness(this, getSpecialBlocks(this, namepattern)));
end

function di = getCoderDIForBlockOutput(this, namepattern, opIdx)
% Get the CoderDI of a block output by searching its block name, signal label, and mapping identifier and by the outport index.
di = getCoderDI(this, checkSearchUniqueness(this, getBlockOutputs(this, namepattern, opIdx)));
end

function di = getCoderDIForState(this, namepattern, dwIdx)
% Get the CoderDI of a state by searching its block name, state name, and mapping identifier and by the dwork index.
di = getCoderDI(this, checkSearchUniqueness(this, getStates(this, namepattern, dwIdx)));
end

function di = getCoderDIForDataStore(this, namepattern)
% Get the CoderDI of a data store by searching its block name, data store name, and mapping identifier.
di = getCoderDI(this, checkSearchUniqueness(this, getDataStores(this, namepattern)));
end

function di = getCoderDIForParameter(this, namepattern)
% Get the CoderDI of a parameter by searching its name and mapping identifier.
di = getCoderDI(this, checkSearchUniqueness(this, getParameters(this, namepattern)));
end
end

methods (Access = private)

function modelingElements = getModelingElements(this)
modelingElements = getTopLevelElementsByTypeName(this.mDataModel, 'ModelingElement');
end

function ret = getRootOutports(this, namepattern)
narginchk(1,2);
elemType = 'coderdi.MERootOutport';
ret = getTopLevelElementsByTypeName(this.mDataModel, elemType);
if nargin > 1
ret = ret(hasNamePattern({ret.blkPath}, namepattern) ...
| hasNamePattern({ret.mappingIdentifier}, namepattern) ...
| hasNamePattern({ret.signalLabel}, namepattern));
end
end

function ret = getRootInports(this, namepattern)
narginchk(1,2);
elemType = 'coderdi.MERootInport';
ret = getTopLevelElementsByTypeName(this.mDataModel, elemType);
if nargin > 1
ret = ret(hasNamePattern({ret.blkPath}, namepattern) ...
| hasNamePattern({ret.mappingIdentifier}, namepattern) ...
| hasNamePattern({ret.signalLabel}, namepattern));
end
end

function ret = getSpecialBlocks(this, namepattern)
narginchk(1,2);
elemType = 'coderdi.MEBlock';
ret = getTopLevelElementsByTypeName(this.mDataModel, elemType);
if nargin > 1
ret = ret(hasNamePattern({ret.blkPath}, namepattern) ...
| hasNamePattern({ret.mappingIdentifier}, namepattern));
end
promptIfEmpty(ret, elemType);
end

function ret = getBlockOutputs(this, namepattern, opIdx)
narginchk(1,3);
elemType = 'coderdi.MEBlockOutput';
ret = getTopLevelElementsByTypeName(this.mDataModel, elemType);
if nargin > 1
ret = ret(hasNamePattern({ret.blkPath}, namepattern) ...
| hasNamePattern({ret.mappingIdentifier}, namepattern) ...
| hasNamePattern({ret.signalLabel}, namepattern));
end
if nargin > 2
ret = ret(opIdx == [ret.outPortIdx]);
end
promptIfEmpty(ret, elemType);
end

function ret = getStates(this, namepattern, dwIdx)
narginchk(1,3);
elemType = 'coderdi.MEState';
ret = getTopLevelElementsByTypeName(this.mDataModel, elemType);
if nargin > 1
ret = ret(hasNamePattern({ret.blkPath}, namepattern) ...
| hasNamePattern({ret.mappingIdentifier}, namepattern) ...
| hasNamePattern({ret.stateName}, namepattern));
end
if nargin > 2
ret = ret(dwIdx == [ret.dworkIdx]);
end
promptIfEmpty(ret, elemType);
end

function ret = getDataStores(this, namepattern)
narginchk(1,2);
elemType = 'coderdi.MEDataStore';
ret = getTopLevelElementsByTypeName(this.mDataModel, elemType);
if nargin > 1
ret = ret(hasNamePattern({ret.blkPath}, namepattern) ...
| hasNamePattern({ret.mappingIdentifier}, namepattern) ...
| hasNamePattern({ret.dataStoreName}, namepattern));
end
promptIfEmpty(ret, elemType);
end

function ret = getParameters(this, namepattern)
narginchk(1,3);
elemType = 'coderdi.MEParameter';
ret = getTopLevelElementsByTypeName(this.mDataModel, elemType);
if nargin > 1
ret = ret((hasNamePattern({ret.parameterName}, namepattern) ...
| hasNamePattern({ret.mappingIdentifier}, namepattern)) ...
& hasNamePattern({ret.representingEntity},'SlEngineParam'));
end
promptIfEmpty(ret, elemType);
end

function me = checkSearchUniqueness(~, me)
if numel(me) > 1
error('Search returns more than one matching modeling element. Please refine your search criteria.');
end
end

function di = getCoderDI(~, me)
di = [];
if numel(me.diAssignments) > 0
diAssignments = me.diAssignments;
max_ts = max(diAssignments.keys);
di = diAssignments{max_ts}.di;
end
end

function coderDIs = getCoderDIs(this)
coderDIs = getTopLevelElementsByTypeName(this.mDataModel, 'CoderDI');
end
end
end

function elems = getTopLevelElementsByTypeName(m, typeName)
tops = m.topLevelElements;
elems = tops(arrayfun(@(ele) isa(ele, typeName), tops));
end

function lIndices = hasNamePattern(names, pattern)
lIndices = cellfun(@(name) numel(regexp(name, pattern))>0, names);
end

function promptIfEmpty(elems, elemType)
if isempty(elems)
warning('No modeling element of type %s is found with the given search criteria.', elemType);
end
end

% LocalWords: insp di ri ro bo mydelay dsm rtansition myparam mf