classdef (Hidden = true) Utils < handle
%UTILS provides general utility methods.
%
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2007-2021 The MathWorks, Inc.

    methods (Static = true) % static methods

        function validateArg(arg, expectedClass)
            % utility function to validate argument types
            narginchk(2, 2);
            if ~ischar(expectedClass)
                rtw.connectivity.ProductInfo.error('target', 'InvalidCharArgument', ...
                    class(expectedClass));
            end
            if ~isa(arg, expectedClass)
                rtw.connectivity.ProductInfo.error('target', 'InvalidArgument', ...
                    expectedClass, class(arg));
            end
        end
        
        % Check if the specified process is alive
        function [alive] = isAlive(pid)
            alive = psUtils('isalive', pid);
        end
                
        function isEnabled = isCommServiceAPIEnabled
        % Enables the new CS/AS infrastructure with SIL/PIL
            isEnabled = strcmp(getenv('USING_CS_API'), '1');
        end
        
        function origEnvVars = setEnvVars(envVars)
            origEnvVars = envVars;
            for i=1:length(envVars)
                origEnvVars(i).value = getenv(envVars(i).name);
                setenv(envVars(i).name,envVars(i).value);
            end
        end
        
        % Launches an executable by passing the cell array of strings that
        % are expected names of the child processes that are about to be spawned
        
        function [exePID, ...
                  exeOutputFile] = launchProcessWithChildProcessNames(command, argString, children)
            [exePID, exeOutputFile] = rtw.connectivity.PsUtils.launchProcess(command, argString, '', children);
        end

        % Launches an executable via a temporary script file and directs
        % executable stdout & stderr to a temporary file.
        %
        % Returns PID for the executable
        % Returns a file for output from the executable
        %
        % tempname is used to create the temporary scriptFile and
        % exeOutputFile files.   tempname generates time dependent
        % temporary names so there will not be any clashes between
        % multiple calls to launchProcess from the same MATLAB instance or
        % from multiple MATLAB's.
        function  [exePID, ...
                  exeOutputFile, ...
                  exeErrorFile] = launchProcess(command, argString, varargin)
                        
              if length(varargin) >= 1 && ~isempty(varargin{1})
                  exePID = rtw.connectivity.PsUtils.launchProcess(command, argString, varargin{:});
              else
                  [exePID, exeOutputFile, exeErrorFile] =... 
                    rtw.connectivity.PsUtils.launchProcess(command, argString, varargin{:});
              end
        end

        % Kills a process given the PID and file argument provided by
        % launchProcess
        function killProcess(exePID, ...
                             exeOutputFile,...
                             varargin)
            
            % Parse optional inputs (defaults are to wait 60 seconds for
            % process to terminate, and not to force close if necessary).
            p = inputParser;
            validationFcn = @(x) isempty(x) || ischar(x);
            addParameter(p,'exeErrorFile','',validationFcn);
            addParameter(p,'WaitTime',60,@isnumeric);
            addParameter(p,'ForceClose',false,@islogical);
            parse(p,varargin{:});
            % Time to wait for process to end:
            waitTime = p.Results.WaitTime;
            % The force close option is only available on Linux platform
            % and will send SIGKILL if SIGTERM doesn't work
            forceClose = p.Results.ForceClose && isunix;
            exeErrorFile = p.Results.exeErrorFile;
            
            % Use psUtils to kill the executable
            if psUtils('isalive', exePID)
                % Disable psUtils warning about failing to terminate
                % process. We will throw our own warning
                % "Connectivity:target:ExeIsAlive" below if the process is
                % not terminated.
                origWarningState = warning('off', 'PIL:internal:PsFcnsFailedToTerminate');
                restoreWarningStateTermFailed = onCleanup(@()warning(origWarningState));
                
                % Disable psUtils warning about process not being alive.
                % The process has terminated after the 'isalive' check and
                % before the call to kill. Can fail silently in this case.
                origWarningState = warning('off', 'PIL:internal:PsFcnsPidNotAlive');
                restoreWarningStateNotAlive = onCleanup(@()warning(origWarningState));
                
                psUtils('kill', exePID);
            end
            
            % Wait until the process has terminated
            timeout = waitTime;
            pause_amt = 0.1;            
            numRetries = timeout / pause_amt;
            exeIsAlive = true;
            for i=1:numRetries
               if ~psUtils('isalive', exePID)
                   exeIsAlive = false;    
                   break;
               end
               pause(pause_amt);               
            end  
            
            if exeIsAlive && forceClose
                % Argument checks should prevent non-Linux platforms from
                % attempting to access this branch:
                assert(isunix,'Force close is limited to Unix platforms.')
                
                % Force the process to close on Linux using SIGKILL if it
                % has not closed nicely after receiving SIGTERM
                errorOccurred = system(sprintf('kill -9 %d',exePID));
                
                if ~errorOccurred
                    % Give the process time to terminate
                    exeIsAlive = true;
                    for i=1:numRetries
                        if ~psUtils('isalive', exePID)
                            exeIsAlive = false;
                            break;
                        end
                        pause(pause_amt);
                    end
                end
            end
            
            % If process is still alive, throw a warning 
            if exeIsAlive
               % warn
               rtw.connectivity.ProductInfo.warning('target', ...
                                                    'ExeIsAlive', ...
                                                    exePID);
            else
                % allow process to fully free up its exeOutputFile
                % resource so that we can delete it successfully                
                exeOutputFileExists = true;
                % disable delete warning
                origWarningState = warning('off', 'MATLAB:DELETE:Permission');
                c = onCleanup(@()warning(origWarningState));
                for i=1:numRetries
                    rtw.connectivity.PsUtils.cleanTempFile(exeOutputFile);
                    rtw.connectivity.PsUtils.cleanTempFile(exeErrorFile);
                    if ~exist(exeOutputFile, 'file') && ~exist(exeErrorFile, 'file')
                        exeOutputFileExists = false;
                        break;
                    end                      
                    pause(pause_amt);                    
                end
                if exeOutputFileExists
                    % warn
                    rtw.connectivity.ProductInfo.warning('target', ...
                                                         'ExeOutputFileExists', ...
                                                          exeOutputFile);
                end                                
            end                                    
        end  
        
        function isOpenNagForModel = isOpenNagForModel(model)
            % find Simulink diagnostic viewer
            nagCtrl = find_dv;
            % check its title string references the model
            if ~isempty(nagCtrl) && ...
                    nagCtrl.Visible && ...
                    ~isempty(strfind(nagCtrl.Title, model))
                isOpenNagForModel = true;
            else
                isOpenNagForModel = false;
            end
        end   
        
        % return the dataset element and element index corresponding to the
        % specified combination of BlockPath, PortType and PortIndex
        %
        % element and elementIdx will be empty if no matching element is
        % found
        function [element, elementIdx] = getDatasetElement(dataset, ...
                                                           blockPath, ...
                                                           portType, ...
                                                           portIndex)
            % search through the data set looking for blockPath, portType 
            % & portIndex combination
            rtw.connectivity.Utils.validateArg(dataset, 'Simulink.SimulationData.Dataset');
            rtw.connectivity.Utils.validateArg(blockPath, 'Simulink.SimulationData.BlockPath');
            rtw.connectivity.Utils.validateArg(portType, 'char');
            rtw.connectivity.Utils.validateArg(portIndex, 'double');
            element = [];
            elementIdx = [];
            for idx = 1:dataset.getLength
               currElement = dataset.getElement(idx); 
               assert(isa(currElement, 'Simulink.SimulationData.Signal'), ...
                      'currElement should be a Simulink.SimulationData.Signal');
               if currElement.BlockPath.isequal(blockPath) && ...
                       (currElement.PortIndex == portIndex) && ...
                       strcmp(currElement.PortType, portType)
                  element = currElement;
                  elementIdx = idx;
                  break;                   
               end                                      
            end             
        end                
        
        function silSubDir = getSilSubDir
            silSubDir = 'sil';
        end
        
        function hostObjSubDir = getHostObjSubDir
            hostObjSubDir = 'hostobj';
        end
        
        function xilSubDir = getXILSubDir(inTheLoopType, harnessName)
            % get xil sub folder considering the atomic subsystem workflow
            arguments
                inTheLoopType char {mustBeMember(inTheLoopType, {'SIL', 'PIL'})}
                harnessName char = '';
            end
            xilSubDir = lower(inTheLoopType);
            if coder.connectivity.XILSubsystemUtils.isAtomicSubsystem(harnessName)
                xilSubDir = fullfile(xilSubDir, ...
                    get_param(harnessName, 'XILAtomicFolderName'));
            end
        end
        
        function [SilHostObjSubDir,...
                relativePathToParent] = getSilHostObjSubDir()
            
            SilHostObjSubDir = fullfile(...
                rtw.connectivity.Utils.getSilSubDir,...
                rtw.connectivity.Utils.getHostObjSubDir);
            
            % this should always match the number of directories in
            % SilHostObjSubDir, so in this case sil/hostobj
            % translates to ../..
            relativePathToParent = fullfile('..','..');
        end
        
        % return the name of the PIL sub-directory of the build directory
        function pilSubDir = getPilSubDir
            pilSubDir = 'pil';
        end
                
        function hostTcNames = getHostToolchainNames()
            % return back the names of the available host-based toolchain
            hostTcNames = coder.make.internal.getHostToolchains;
            
            % Also add "Automatically locate an installed toolchain" since
            % this always resolves to a host toolchain
            hostTcNames{end+1} = coder.make.internal.getInfo('default-toolchain');
        end
        
        function success = buildInfoAddSrcFileToSilSkipGroup(model, srcFile)
            % Calling from tlc, where errors will not be shown
            success = 1;
            
            % add file to special group in buildInfo
            group = 'SkipForSil';
            rtw.connectivity.Utils.buildInfoAddSrcFileToGroup(model, srcFile, group);            
        end
        
        function success = buildInfoAddSrcFileToInTheLoopSkipGroup(model, srcFile)            
            % Calling from tlc, where errors will not be shown
            success = 1;
            
            % add file to special group in buildInfo
            group = 'SkipForInTheLoop';
            rtw.connectivity.Utils.buildInfoAddSrcFileToGroup(model, srcFile, group);
        end
        
        function success = buildInfoAddSrcFileToExtModeGroup(model, srcFile)            
            % Calling from tlc, where errors will not be shown
            success = 1;
            
            % add file to special group in buildInfo
            group = 'EXT_MODE';
            rtw.connectivity.Utils.buildInfoAddSrcFileToGroup(model, srcFile, group);
        end
        
        function success = buildInfoAddDefineToOptsGroup(model, defineMacro)       
            % Calling from tlc, where errors will not be shown
            success = 1;
            
            % add macro to OPTS group in buildInfo
            group = 'OPTS';
            rtw.connectivity.Utils.buildInfoAddDefineToGroup(model, defineMacro, group);
        end
      
        function buffer = includeReferencedModelsHeaders(model, tgtType)
            % return a string containing the #include to the
            % ref_models_GroupTypes.h files of the models directly
            % referenced by "model"
            
            % use binfo get the referenced models
            binfo = coder.internal.infoMATFileMgr(...
                'loadNoConfigSet', 'binfo', model, tgtType);
            modelRefs = binfo.modelRefs;
            
            folderName = rtw.connectivity.Utils.getStubFolderName;
            stubSuffix = rtw.connectivity.Utils.getStubHeaderSuffix(true);
            
            buffer = [];
            for idx = 1:numel(modelRefs)
                hasOwner = ~isempty(get_param(model, 'MDXOwner'));
                if hasOwner
                    currMdlRef = modelRefs{idx};
                    % match a model reference to its build directory
                    modelRefAllIndex = strcmp(currMdlRef, binfo.modelRefsAll);
                    modelRefBuildDir = binfo.modelRefsBuildDirsAll(modelRefAllIndex);
                    assert(numel(modelRefBuildDir) == 1, 'unexpected number of matching dirs');
                    % get the relative path to the $R_GroupTypes.h file
                    relPathToRefHeader = fullfile(...
                        char(modelRefBuildDir), ...
                        folderName,...
                        [currMdlRef stubSuffix]);
                    
                    buffer = sprintf('%s\n#include "%s"',buffer, relPathToRefHeader);
                else
                    % no need to include this referenced model header
                end
            end
        end
        
        function success = addHeaderFileToBuildInfo(model, file, addOwnerPath)
            % Calling from tlc, where errors will not be shown
            success = 1;
            
            if nargin < 3
                addOwnerPath = false;
            end
            
            % get buildInfo
            modelCodegenMgr = coder.internal.ModelCodegenMgr.getInstance(model);
            buildInfo = modelCodegenMgr.BuildInfo;
            
            [fileDir, fileName, fileExt] = fileparts(file);
            % add include paths
            if addOwnerPath
                MDXOwner  = get_param(model, 'MDXOwner');
                buildDir = RTW.getBuildDir(MDXOwner);
                % create the full path to the 'test' folder
                fullPathToFolder = fullfile(buildDir.BuildDirectory,...
                    rtw.connectivity.Utils.getStubFolderName);
                
                buildInfo.addIncludePaths({fileDir, fullPathToFolder});
            else
                buildInfo.addIncludePaths(fileDir);
            end
            % add include file
            fileNoPath = [fileName fileExt];
            buildInfo.addIncludeFiles(fileNoPath, fileDir);
        end
        
        function postProcessSystemHeadersWithNoOwner(model, accessFcnHFile, modelTypesHFile)
            % called only by referenced models that have no MDXOwner
            
            % look in the 'stub' folder for the header file created for groups
            % that have no MDXOwner
            buildDir = RTW.getBuildDir(model);
            list = dir(fullfile(buildDir.ModelRefRelativeBuildDir,...
                rtw.connectivity.Utils.getStubFolderName));
            
            for idx = 1:numel(list)
                if ~list(idx).isdir
                    fullName = fullfile(list(idx).folder, list(idx).name);
                    [~, headerName] = fileparts(list(idx).name);
                    % read the file and add a guard
                    headerTag = ['RTW_HEADER_'  headerName '_h_'];
                    content = fileread(fullName);
                    if ~isempty(accessFcnHFile)
                        % include access_functions.h
                        content = sprintf('#include "%s"\n%s', ...
                            accessFcnHFile, content);
                    end
                    if ~isempty(modelTypesHFile)
                        % include model_types.h
                        content = sprintf('#include "%s"\n%s', ...
                            modelTypesHFile, content);
                    end
                    fid = fopen(fullName, 'w');
                    fprintf(fid,...
                        '#ifndef %s\n#define %s\n\n%s\n#endif /* %s */',...
                        headerTag, headerTag, content, headerTag);
                    fclose(fid);
                    % call cBeautifierWithOptions and add to buildInfo
                    rtwprivate('cBeautifierWithOptions', fullName, model, true);
                    rtw.connectivity.Utils.addHeaderFileToBuildInfo(model, fullName);
                end
            end
        end
        
        function [relFolderName, absFolderName] = getStubFolderName(modelName, isModelRefRTWTarget)
            % return the relative and absolute name of the stub folder
            relFolderName = 'test';
            if nargin > 0
                buildDir = RTW.getBuildDir(modelName);
                if isModelRefRTWTarget
                    absFolderName = fullfile(buildDir.CodeGenFolder,...
                        buildDir.ModelRefRelativeBuildDir, relFolderName);
                else
                    absFolderName = fullfile(buildDir.BuildDirectory, relFolderName);
                end
            else
                absFolderName = '';
            end
        end
        
        function suffix = getStubHeaderSuffix(withExtention)
            string = '_GroupTypes';
            if withExtention
                suffix = [string '.h'];
            else
                suffix = string;
            end
        end
        
        function fileName = createStubFolderAndGetFilePaths(modelName, isModelRefRTWTarget, file)
            % combine calls to createStubFolder and getFilePaths to reduce
            % the number of calls from tlc to MATLAB
            rtw.connectivity.Utils.createStubFolder(modelName, ...
                isModelRefRTWTarget);
            fileName = rtw.connectivity.Utils.getFilePaths(file, ...
                modelName, isModelRefRTWTarget);
        end
        
        
        function createStubFolder(modelName, isModelRefRTWTarget)
            % create a sub-folder in the current build dir, do nothing if
            % the sub-folder already exists
            [~, fullName] = rtw.connectivity.Utils.getStubFolderName(modelName, isModelRefRTWTarget);
            if ~exist(fullName,'dir')
                mkdir(fullName);
            end
        end
        
        
        function fileName = getFilePaths(file, modelName, isModelRefRTWTarget)
            % get the relative path to file (e.g. sub-folder/file) and
            % get the absolute path to file
            [relFolderName, absFolderName] = ...
                rtw.connectivity.Utils.getStubFolderName(modelName, isModelRefRTWTarget);
            
            fileName.RelativePath = fullfile(relFolderName, file);
            fileName.AbsolutePath = fullfile(absFolderName, file);
            if isModelRefRTWTarget
                % also the relative path to the slprj folder
                buildDir = RTW.getBuildDir(modelName);
                fileName.RelativePathToSlprj = fullfile(...
                    buildDir.ModelRefRelativeBuildDir, file);
                % and the relative path to the slprj/stub folder
                fileName.RelativePathToSlprjStub = fullfile(...
                    buildDir.ModelRefRelativeBuildDir, ...
                    relFolderName, file);
            end
        end
        
               
        function ownerInfo = getMDXOwnerInfo(model)
            % return the boolean HasMDXOwner and the StubHeaderFileName
            % name if the MDXOwner is set
            MDXOwner  = get_param(model, 'MDXOwner');
            
            if isempty(MDXOwner)
                ownerInfo.HasMDXOwner = false;
                % no stubbed header file in this case
                ownerInfo.StubHeaderFileName = [];
            else
                ownerInfo.HasMDXOwner = true;
                % get the name for the model_GroupType file, it will be
                % included by the FC level DataGroupHeaderFile
                ownerInfo.StubHeaderFileName = [model ...
                    rtw.connectivity.Utils.getStubHeaderSuffix(false)];
            end
        end
        
        
        function isSilAndPWS = isSilAndPWSBuild(model)
            modelCodegenMgr = coder.internal.ModelCodegenMgr.getInstance(model);
            assert(~isempty(modelCodegenMgr),...
                ['The rtw.connectivity.Utils.isSilAndPWSBuild method can only be '...
                'called during the code generation process.']);
            isSilAndPWS = modelCodegenMgr.MdlRefBuildArgs.XilInfo.IsSilAndPws;
        end
        
        function isSilSim = isSil(model)
            modelCodegenMgr = coder.internal.ModelCodegenMgr.getInstance(model);
            assert(~isempty(modelCodegenMgr),...
                ['The rtw.connectivity.Utils.isSil method can only be '...
                'called during the code generation process.']);
            isSilSim = modelCodegenMgr.MdlRefBuildArgs.XilInfo.IsSil;
        end
        
        function isPilSim = isPil(model)
            modelCodegenMgr = coder.internal.ModelCodegenMgr.getInstance(model);
            assert(~isempty(modelCodegenMgr),...
                ['The rtw.connectivity.Utils.isPil method can only be '...
                'called during the code generation process.']);
            isPilSim = modelCodegenMgr.MdlRefBuildArgs.XilInfo.IsPil;
        end
        
        function deleteSILPILFiles(buildDir)
            % Removing .c/.cpp files aims to remove the possibility of "module".c and
            % "module".cpp coexisting which can lead to unexpected results for
            % makefile-based build processes
            %
            % SIL dir
            silDir = fullfile(buildDir, rtw.connectivity.Utils.getSilSubDir);
            % remove .c/.cpp
            rtw.connectivity.PsUtils.deleteFiles(fullfile(silDir, '*.c'));
            rtw.connectivity.PsUtils.deleteFiles(fullfile(silDir, '*.cpp'));
            % remove the marker file to make sure object code rebuilds, which
            % makes sure header file (e.g. preprocessor_check.h) changes 
            % are picked up - see also g762937.
            rtw.connectivity.PsUtils.deleteFiles(fullfile(silDir, 'rtw_proj.tmw'));
            
            % PIL dir
            pilDir = fullfile(buildDir, rtw.connectivity.Utils.getPilSubDir);
            % remove .c/.cpp
            rtw.connectivity.PsUtils.deleteFiles(fullfile(pilDir, '*.c'));
            rtw.connectivity.PsUtils.deleteFiles(fullfile(pilDir, '*.cpp'));
            rtw.connectivity.PsUtils.deleteFiles(fullfile(pilDir, 'rtw_proj.tmw'));
            
            % Delete buildInfo.mat from the sil/pil sub-folders. This ensures we always
            % create a new sil/pil buildInfo for new codegen
            rtw.connectivity.PsUtils.deleteFiles(fullfile(silDir, 'buildInfo.mat'));
            rtw.connectivity.PsUtils.deleteFiles(fullfile(pilDir, 'buildInfo.mat'));
            
            % Delete buildInfo from the sil/hostobj folder for new builds
            silHostObjDir = fullfile(buildDir, rtw.connectivity.Utils.getSilHostObjSubDir);
            rtw.connectivity.PsUtils.deleteFiles(fullfile(silHostObjDir, 'buildInfo.mat'));
            
        end
        
        function isValid = checkUnicode2NativeRoundTrip(unicodeStr, encoding)
            %
            % Check to see whether a unicode string can be "round tripped"
            % through to native representation and back to unicode without
            % losing information.
            %
            % This function can be used to detect cases where information is
            % lost over the Simulink (unicode) <-> s-function /
            % SimStruct (native) boundary.
            % 
            % The conversion from unicode to native results in characters
            % that are not representable being replaced with the
            % substitution character, e.g. char(26).
            %
            if nargin < 2
               % MATLAB's default encoding scheme
               encoding = ''; 
            end
            nativeStr = unicode2native(unicodeStr, encoding);
            roundTripUnicode = native2unicode(nativeStr, encoding);
            isValid = strcmp(unicodeStr, roundTripUnicode);            
        end
    end
    
    methods (Hidden, Static = true, Access = 'public')
        % delete temporary file / dir
        function cleanTempFile(fileName)
            rtw.connectivity.PsUtils.cleanTempFile(fileName);
        end
    end
       
    methods (Static = true, Access = 'private')        
        
        function buildInfoAddSrcFileToGroup(model, srcFile, group)
            % get buildInfo
            modelCodegenMgr = coder.internal.ModelCodegenMgr.getInstance(model);
            buildInfo = modelCodegenMgr.BuildInfo;
            
            % add src file
            [path, srcName, ext] = fileparts(srcFile);
            buildInfo.addSourceFiles([srcName ext], path, group);
        end
        
        function buildInfoAddDefineToGroup(model, defineMacro, group)
            % get buildInfo
            modelCodegenMgr = coder.internal.ModelCodegenMgr.getInstance(model);
            buildInfo = modelCodegenMgr.BuildInfo;
            
            % add define macro to build info
            buildInfo.addDefines(defineMacro, group);     
        end
    end
end


