% AbstractBatchHelper - batch helper base case

% Copyright 2010-2020 The MathWorks, Inc.

classdef ( Hidden ) AbstractBatchHelper < handle

    properties ( Abstract, Constant, GetAccess = protected )
        % Currently disabled, leave this as an instance property in case we
        % figure things out.
        AllowVectorizedBatch
    end

    properties ( GetAccess = private, SetAccess = immutable )
        % Should we warn about use of API1 names.
        WarnOnOldNameUsage
    end

    properties
        % Note that configuration may need to be set by the
        % batch function, hence it is publicly accessible.
        Configuration = '';
    end

    properties ( Constant, GetAccess = protected )
        EmptyScriptTaskStruct = struct('ScriptName', '', 'WorkspaceIn', struct([]));
        EmptyFunctionTaskStruct = struct('FunctionToRun', [],'NumArgsOut', [], 'ArgsIn', {});
    end
    properties ( GetAccess = protected, SetAccess = immutable )
        ProfHelper
    end
    properties (Access = protected)
        % The parameters that users can specify via P-V pairs to batch.
        % Note that we need to know whether or not the user actually
        % specified all of these parameters so we can pass them to
        % createJob and createTask correctly.

        % Args related only to batch - note that Configuration is publicly
        % accessible
        PoolSize                          = 0;
        UserSpecifiedPoolSize             = false;
        WorkerCWD                         = '';
        UserSpecifiedWorkerCWD            = false;

        % Args related to jobs
        JobName                           = '';
        UserSpecifiedJobName              = false;
        AutoAttachFiles                   = true;
        UserSpecifiedAutoAttachFiles      = false;
        AutoAddClientPath                 = true;
        FileDependencies                  = {};
        UserSpecifiedFileDependencies     = false;
        PathDependencies                  = {};
        UserSpecifiedPathDependencies     = false;
        EnvironmentVariables              = {};
        UserSpecifiedEnvironmentVariables = false;

        % Set the default value for the ApiTag. These are only specifiable via
        % "undoc:" properties. Therefore, we do not add "UserSpecified..."
        % flags to go along with these two properties, since users can never
        % specify either of these.
        ApiTag                            = parallel.internal.cluster.BatchJobConstants.BatchJobTag;
        Tag                               = '';
        
        % Args related to tasks
        % NB Default for CaptureDiary is true
        CaptureDiary                      = true;
        UserSpecifiedCaptureDiary         = false;


        UserSpecifiedConfiguration        = false;
        UserSpecifiedWorkspace            = false;

        % Other properties
        IsRunScript                       = false;

        % A structure to store the tasks themselves
        TasksToRun;
    end

    methods ( Access = private )
        % This will be called prior to defining the configuration. If you
        % object, throw an error. Uses ProfHelper which knows the correct API
        % stuff to use.
        function checkConfigurationOk( obj, configName )
            if isempty(configName) && ischar(configName)
                % ok
                return
            end
            if ~iIsString(configName)
                error(message('parallel:convenience:BatchProfileString', obj.ProfHelper.PropertyName));
            end
            if ~obj.ProfHelper.nameExists( configName )
                obj.ProfHelper.throwNoSuchConfig( configName );
            end
        end
    end

    methods ( Abstract )
        % Actually create the batch job and tasks, and submit.
        job = doBatch( obj, cluster );
    end

    % Public get/set methods
    methods
        function set.Configuration( obj, configName )
        % Check the that the configuration is actually exists
        % before setting it.  If the configName was an empty string, then
        % just set it.
            obj.checkConfigurationOk( configName );
            obj.Configuration = configName;
        end
    end

    % Public methods
    methods
        function obj = AbstractBatchHelper(pch, scriptOrFunctionToRun, args)

            % For now, never warn.
            obj.WarnOnOldNameUsage = false;

            % Store our ProfileConfigHelper
            obj.ProfHelper = pch;

            % After the call to checkIfScriptSupplied, we know that scriptOrFunctionToRun
            % must be one of the following (otherwise it would have errored):
            % If a script:
            %   a string,
            %   an array of strings,
            %   a cell array of strings.
            %
            % If a function:
            %   a string,
            %   a function handle,
            %   a cell array of function handles,
            %   an array of strings,
            %   a cell array of strings,
            %   a cell array of strings and function handles.
            possibleNargout = [];
            if ~isempty(args)
                possibleNargout = args{1};
            end
            obj.IsRunScript = obj.checkIfScriptSupplied(scriptOrFunctionToRun, possibleNargout);

            % If running a function, then we need to have at least 1 arg - the
            % number of output arguments for the task
            if ~obj.IsRunScript 
                if isempty(args)
                    error(message('parallel:convenience:NumOutputsRequiredForFunction'));
                elseif ~iIsValidNargOutArgument(possibleNargout)
                    error(message('parallel:convenience:BatchInvalidNumOutputs'));
                end
            end

            % Convert scriptOrFunctionToRun to a cell array
            if ~iscell(scriptOrFunctionToRun)
                if ischar(scriptOrFunctionToRun)
                    scriptOrFunctionToRun = cellstr(scriptOrFunctionToRun);
                else
                    scriptOrFunctionToRun = {scriptOrFunctionToRun};
                end
            end

            if obj.IsRunScript
                obj.parseScriptInputs(scriptOrFunctionToRun, args);
            else
                obj.parseFunctionInputs(scriptOrFunctionToRun, args);
            end
        end

        function needsWorkspace = needsCallerWorkspace(obj)
        % needsCallerWorkspace - Do we need the caller workspace to be set for the
        % single script task?

        % The only scenario in which we need the caller workspace
        % is if there is only 1 task to run, and that task happens
        % to be a script and there is no workspace associated with it, and that
        % empty workspace was not user specified.
            needsWorkspace = obj.IsRunScript && (numel(obj.TasksToRun) == 1) && ...
                isempty(obj.TasksToRun(1).WorkspaceIn) && ~obj.UserSpecifiedWorkspace;
        end

        function filterAndSetCallerWorkspace(obj, varNames, varValues)
        % filterAndSetCallerWorkspace - Filter the workspace and set appropriate
        % variables in the task's workspace.  This is only supported if
        % there we are running scripts, there is only 1 task and that task currently
        % does not have a workspace.
            if ~obj.needsCallerWorkspace
                error(message('parallel:convenience:CallerWorkspaceNotSupported'));
            end
            
            varNames = varNames(:);
            varValues = varValues(:);
            shouldNotStoreInWorkspaceFcn = @(x) isa( x, 'distcomp.object') || ...
                        isa( x, 'parallel.internal.customattr.CustomPropTypes' ) || ...
                        isa( x, 'Composite' ) || ...
                        isa( x, 'distributed' );
            
            storeInWorkspace = ~cellfun(shouldNotStoreInWorkspaceFcn, varValues);
            workspace = cell2struct(varValues(storeInWorkspace), varNames(storeInWorkspace), 1);
            obj.TasksToRun(1).WorkspaceIn = workspace;
        end
    end

    methods ( Access = private )
        function parseFunctionInputs(obj, functionsToRun, args)
        % parseFunctionInputs - Parse the input args to batch when the first argument
        % represents the functions that need to run.

        % If the input to batch was a function handle(or array of function handles),
        % then args{1} = nargout (or array of nargout)
        % and args{2} = cell array of input arguments, or the start of the P-V pairs to batch
        % We allow the following:
        % 1 function + 1 nargout + 1 nargin
        % 1 function + 1 nargout + 1..n nargin
        % n functions + n nargout + n nargin

        % If we get into here, we already know that args{1} is a valid nargout argument
        % (it is checked in the constructor)
            numArgsOut = args{1};
            % Always remove the nargout from the args
            argIndexToRemove = 1;
            % Now check if the next arg is an input argument array
            functionInputArgs = {};
            if length(args) > 1
                functionInputArgs = args{2};
                if iIsString(functionInputArgs)
                    % User didn't specify any input arguments, and arg(2) is actually the first of the P-V pairs
                    % so set the input args to an empty cell.
                    functionInputArgs = {};
                else
                    % Remove both the nargout and argsin from the args
                    argIndexToRemove = 1:2;
                end
            end

            inputArgumentsError = MException(message('parallel:convenience:InputsMustBeCellOrVector'));
            if ~iscell(functionInputArgs)
                throw(inputArgumentsError);
            end

            % Check the functionsToRun, numArgsOut and functionInputArgs for consistency.

            % Check the functionInputArgs
            
            % If functionInputArgs is a nested cell array {C1,...,Cm} where
            % each element Ci is a valid cell array of task args, warn that
            % the behaviour has changed; this is no longer interpreted as
            % an attempt to use vectorized batch. See g2291398.
            isValidArgsCell = @(args) iscell(args) && (isempty(args) || isvector(args));
            isOldVectorizedSyntax = isscalar(functionInputArgs) && all(cellfun(isValidArgsCell, functionInputArgs));
            if isOldVectorizedSyntax
                parallel.internal.warningNoBackTrace(message('parallel:convenience:BatchFunctionNestedCellArray'));
            end
            
            if ~(isvector(functionInputArgs) || isempty(functionInputArgs))
                throw(inputArgumentsError);
            end
            % Convert to a 1 x 1 cell array
            functionInputArgs = {functionInputArgs};

            % Check functionsToRun
            if numel(functionsToRun) == 1
                % Make functionsToRun the same size as functionInputArgs
                functionsToRun = repmat(functionsToRun, size(functionInputArgs));
            else
                % Check functionsToRun is the same size as functionInputArgs
                if ~isequal(size(functionsToRun), size(functionInputArgs))
                    error(message('parallel:convenience:InputFunctionsIncorrectSize'));
                end
            end

            % Check numArgsOut
            if numel(numArgsOut) == 1
                % Make numArgsOut the same size as functionInputArgs
                numArgsOut = repmat(numArgsOut, size(functionInputArgs));
            else
                if ~isequal(size(numArgsOut), size(functionInputArgs))
                    % Check numArgsOut is the same size as functionInputArgs
                    error(message('parallel:convenience:NumOutputArgumentsIncorrectSize'));
                end
            end

            % Remove the relevant arguments from the args
            args(argIndexToRemove) = [];
            
            % Having removed the functionInputArgs which maintain their
            % original type, we are free to convert any other string inputs
            % to char.
            args = convertStringsToChars(args);

            % This is used to check if a valid property has been specified but the
            % wrong version of batch has been used
            % e.g. batch(@myfunction, 1, {}, 'Workspace', struct())
            % In this case we want the error message to be more informative.
            % See g953797.
            scriptProps     = {'Workspace'};
            scriptPropsIdx = cellfun(@(x) any(find(strcmpi(x, args))), scriptProps);
            if any(scriptPropsIdx)
                error(message('parallel:batch:InvalidBatchFunctionInput', scriptProps{find(scriptPropsIdx,1)}));
            end

            % Now parse the rest of the (common) arguments.
            obj.parseCommonInputs(args);

            % Pre-allocate the tasks structure
            obj.TasksToRun = repmat(obj.EmptyFunctionTaskStruct, numel(functionsToRun), 1);
            % Sort out the args for the tasks
            for ii = 1:numel(functionsToRun)
                obj.TasksToRun(ii).FunctionToRun = functionsToRun{ii};
                obj.TasksToRun(ii).NumArgsOut = numArgsOut(ii);
                obj.TasksToRun(ii).ArgsIn = functionInputArgs{ii};
            end

            % Make sure the scripts are added to the filedependencies and job name
            obj.addFileDependenciesAndJobName(functionsToRun);
        end

        function parseScriptInputs(obj, scriptsToRun, args)
        % parseScriptInputs - Parse the input args to batch when the first argument
        % represents the scripts that need to run.

        % We can have one of the following:
        % 1 script + 0..1 workspaces = 1 task, or nTasks = nScripts
        % 1 script + 2..n workspaces = 2..n tasks, or nTasks = nWorkspaces
        % n scripts + n workspaces = n tasks, or nTasks = nScripts

        % For script inputs we are free to convert any string inputs to
        % char
            args = convertStringsToChars(args);
            
        % Find the workspace PV pair
            workspaceIndex = find(strcmpi('Workspace', args));
            workspaceValue = struct([]);
            argIndexToRemove = [];
            if ~isempty(workspaceIndex)
                % We found a workspace p, now see if we can find the v
                if numel(args) <= workspaceIndex
                    error(message('parallel:convenience:WorkspacePropertyValueRequired'));
                end
                workspaceValue = args{workspaceIndex+1};
                obj.UserSpecifiedWorkspace = true;

                if ~isstruct(workspaceValue)
                    error(message('parallel:convenience:CallerWorkspaceMustBeStruct'));
                end
                % Remove the workspace PV pair from the args
                argIndexToRemove = workspaceIndex:workspaceIndex+1;
            end

            numScriptsSupplied = numel(scriptsToRun);
            numWorkspaces = numel(workspaceValue);
            isVectorized = (numScriptsSupplied > 1) || (numWorkspaces > 1);
            if isVectorized
                obj.errorIfVectorizedBatchNotAllowed;
            end

            % Check for consistency between scripts and workspaces
            % If more than 1 script was specified, then there must be a corresponding
            % number of workspaces.  Note that it is OK to have 1 script, but multiple
            % workspaces.
            if (numScriptsSupplied > 1) && ~isequal(size(workspaceValue), size(scriptsToRun))
                error(message('parallel:convenience:WorkspaceStructIncorrectSize'));
            end
            if (numScriptsSupplied == 1) && (numWorkspaces > numScriptsSupplied)
                % One script, multiple workspaces, so make the script name the same size as the
                % workspaces
                scriptsToRun = cellstr(repmat(scriptsToRun, size(workspaceValue)));
            end

            % Remove the relevant arguments from the args
            args(argIndexToRemove) = [];
            % Now parse the rest of the (common) arguments.
            obj.parseCommonInputs(args);

            % Pre-allocate the tasks structure
            obj.TasksToRun = repmat(obj.EmptyScriptTaskStruct, numel(scriptsToRun), 1);
            % Set the argument info for all the tasks.
            for ii = 1:numel(scriptsToRun)
                obj.TasksToRun(ii).ScriptName = scriptsToRun{ii};
                if ~isempty(workspaceValue)
                    obj.TasksToRun(ii).WorkspaceIn = workspaceValue(ii);
                end
            end
            % Make sure the scripts are added to the filedependencies and job name
            obj.addFileDependenciesAndJobName(scriptsToRun);

            % And check that pool is set to 0 if we are in vectorized mode
            if isVectorized && obj.UserSpecifiedPoolSize && (obj.PoolSize(end) > 0)
                error(message('parallel:convenience:InvalidPoolForVectorizedBatch'));
            end
        end

        function parseCommonInputs(obj, args)
        % parseCommonInputs - Parses the args that are common to both functions and
        % scripts.  Note that this does not check for consistency between arguments
        % (e.g.  that pool size cannot be > 0 if there is an array of
        % scripts/functions)
            import parallel.internal.apishared.ConversionContext
            
        % NB The order of the allowedProps is important because of the switch statement
        % below.  IF YOU ADD MORE PROPERTIES HERE, MAKE SURE THE ORDER MATCHES WITH
        % THE setFcns ORDER.
        %
        % undoc:ApiTag and undoc:Tag are to allow toolbox clients using 'batch' to override ApiTag
        % and Tag during construction. (Changing it later causes confusion for the JobMonitor)
            allowedProps    = { 'Profile', 'AutoAttachFiles', 'AttachedFiles', ...
                                'AdditionalPaths', 'AutoAddClientPath',...
                                'CurrentFolder', 'CaptureDiary', 'EnvironmentVariables', 'Pool', ...
                                'undoc:ApiTag', 'undoc:Tag'};
            % We accept 'deprecated' names, and warn if 'WarnOnOldNameUsage' is true.
            deprecatedProps = ConversionContext.Batch.convertToApi1( allowedProps );
            setFcns         = { @setConfigFromArg, ...
                                @setAutoAttachFromArg, ...
                                @setFileDepsFromArg, ...
                                @setPathDepsFromArg, ...
                                @setAutoAddClientPathFromArg, ...
                                @setCurrentDirFromArg, ...
                                @setCaptureDiaryFromArg, ...
                                @setEnvironmentVariablesFromArg, ...
                                @setMatlabpoolFromArg, ...
                                @setApiTagFromArg, ...
                                @setTagFromArg };

            % Convert the args to a parseable format
            [allProps, allValues] = parallel.internal.convertToPVArrays(args{:});

            % Check that each property is unique amongst the allowed parameters
            for i = 1:numel(allProps)
                thisProp  = allProps{i};
                thisValue = allValues{i};
                % Find this property name in each of the sets of properties
                indexInProps      = find(strcmpi(thisProp, allowedProps));
                indexInDeprecated = find(strcmpi(thisProp, deprecatedProps));

                if isempty(indexInProps) && isempty(indexInDeprecated)
                    if strcmpi('matlabpool', thisProp)
                        error(message('parallel:batch:MatlabpoolInvalidBatchInput', thisProp));
                    else
                        error(message('parallel:batch:UnrecognizedBatchInput', thisProp));
                    end
                elseif isempty( indexInProps )
                    % deprecated match
                    fcnIndex = indexInDeprecated;
                    if obj.WarnOnOldNameUsage
                        warning(message('parallel:convenience:DeprecatedBatchArgument', deprecatedProps{ indexInDeprecated }, allowedProps{ indexInDeprecated }));
                    end
                    propertyNameForError = deprecatedProps{indexInDeprecated};
                else
                    % ordinary match
                    propertyNameForError = allowedProps{indexInProps};
                    fcnIndex = indexInProps;
                end

                % Set the property using the appropriate set*FromArg method.
                fcnH = setFcns{ fcnIndex };
                fcnH( obj, propertyNameForError, thisValue );
            end

            % CurrentDirectory
            if isempty(obj.WorkerCWD) && ~obj.UserSpecifiedWorkerCWD
                % If the user hasn't specified anything we will send over pwd
                obj.WorkerCWD = pwd;
            end
        end

        function addFileDependenciesAndJobName(obj, functionsOrScripts)
        % addFileDependenciesAndJobName - Adds the functions/scripts to the file
        % dependencies and sets the job name if it is not already set.

        % Add the name of the functions/scripts being run to the FileDependencies - but check
        % that they exist first otherwise the job creation will error.  Also check that
        % the file doesn't already exist in the file dependencies.
            for ii = 1:numel(functionsOrScripts)
                filename = functionsOrScripts{ii};
                if isa(filename, 'function_handle')
                    filename = func2str(filename);
                end
                assert(ischar(filename), 'parallel:convenience:FunctionOrScriptStringRequired', 'Function or script name should be a string.');
                % Set the name of the job to be the name of the first function or script, unless it has already been
                % set by the user.
                if (ii == 1) && ~obj.UserSpecifiedJobName && isempty(obj.JobName)
                    obj.JobName = filename;
                end
            end
        end


    end

    % A selection of functions used by parseCommonInputs to set the properties
    % of the object from an argument. These methods are all named after the
    % corresponding property in this object.
    methods ( Access = private )
        function setConfigFromArg( obj, propertyNameForError, thisValue )
            if ~iIsString(thisValue)
                error(message('parallel:convenience:StringInputRequired', propertyNameForError));
            end
            obj.Configuration = thisValue;
            obj.UserSpecifiedConfiguration = true;
        end
        function setAutoAttachFromArg( obj, propertyNameForError, thisValue )
            if ~( islogical(thisValue) && isscalar(thisValue) )
                error(message('parallel:convenience:LogicalScalarRequired', propertyNameForError));
            end
            obj.AutoAttachFiles = thisValue;
            obj.UserSpecifiedAutoAttachFiles = true;
        end
        function setAutoAddClientPathFromArg( obj, propertyNameForError, thisValue )
            if ~( islogical(thisValue) && isscalar(thisValue) )
                error(message('parallel:convenience:LogicalScalarRequired', propertyNameForError));
            end
            obj.AutoAddClientPath = thisValue;
        end
        function setFileDepsFromArg( obj, propertyNameForError, thisValue )
            if iIsString(thisValue)
                thisValue = {thisValue}; % Convert a char vector to a cellstr (see g1954140)
            elseif ~( iscell(thisValue) && all(cellfun(@iIsString, thisValue)) )
                error(message('parallel:convenience:StringOrCellstrRequired', propertyNameForError));
            end
            obj.FileDependencies = thisValue;
            obj.UserSpecifiedFileDependencies = true;
        end
        function setPathDepsFromArg( obj, propertyNameForError, thisValue )
            if iIsString(thisValue)
                % Wrap a single string in a cell array for convenience
                thisValue = {thisValue};
            elseif ~( iscell(thisValue) && all(cellfun(@iIsString, thisValue)) )
                error(message('parallel:convenience:CellstrRequired', propertyNameForError));
            end
            obj.PathDependencies = thisValue;
            obj.UserSpecifiedPathDependencies = true;
        end
        function setCurrentDirFromArg( obj, propertyNameForError, thisValue )
            if ~iIsString(thisValue)
                error(message('parallel:convenience:StringInputRequired', propertyNameForError));
            end
            obj.WorkerCWD = thisValue;
            obj.UserSpecifiedWorkerCWD = true;
        end
        function setCaptureDiaryFromArg( obj, propertyNameForError, thisValue )
            if ~( islogical(thisValue) && isscalar(thisValue) )
                error(message('parallel:convenience:LogicalScalarRequired', propertyNameForError));
            end
            obj.CaptureDiary = thisValue;
            obj.UserSpecifiedCaptureDiary = true;
        end
        function setEnvironmentVariablesFromArg( obj, propertyNameForError, thisValue )
            if iIsString(thisValue)
                % Wrap a single string in a cell array for convenience
                thisValue = {thisValue};
            elseif ~( iscell(thisValue) && all(cellfun(@iIsString, thisValue)) )
                error(message('parallel:convenience:CellstrRequired', propertyNameForError));
            end
            obj.EnvironmentVariables = thisValue;
            obj.UserSpecifiedEnvironmentVariables = true;
        end
        function setMatlabpoolFromArg( obj, propertyNameForError, thisValue )
            if ~iIsValidPoolSize( thisValue )
                error(message('parallel:convenience:PoolWorkerLimits', propertyNameForError));
            end
            obj.PoolSize = thisValue;
            obj.UserSpecifiedPoolSize = false;
        end
        function setApiTagFromArg( obj, propertyNameForError, thisValue )
            if ~iIsString(thisValue)
                error(message('parallel:convenience:StringInputRequired', propertyNameForError));
            end
            obj.ApiTag = thisValue;
        end
        function setTagFromArg( obj, propertyNameForError, thisValue )
            if ~iIsString(thisValue)
                error(message('parallel:convenience:StringInputRequired', propertyNameForError));
            end
            obj.Tag = thisValue;
        end
    end


    methods ( Access = protected )

        function [functionToRun, numArgsOut, allArgsIn, pvPairs, taskDeps] = ...
                getCreateTaskInputs(obj, diaryPropName, configPropName)
        % getCreateTaskInputs Converts TasksToRun into arguments that can be
        % passed into the job.createTask method

            % NB Configuration must be specified first in the list, otherwise
            % values set in the configuration will override any user-specified
            % ones. Empty configPropName implies that no Configuration-type PV
            % is needed (as is the case for API-2).
            if ~isempty( configPropName ) && ...
                    ( obj.UserSpecifiedConfiguration || ~isempty( obj.Configuration ) )
                pvPairs = { configPropName, obj.Configuration };
            else
                pvPairs = {};
            end

            % Always add CaptureDiary - default value for this is true.  It
            % will only be false if the user actually specified it.
            pvPairs          = [pvPairs, diaryPropName, obj.CaptureDiary];

            % Use vectorized task creation since all tasks will use
            % the same task function.
            allArgsIn = repmat({{}}, numel(obj.TasksToRun), 1);
            functionsToAnalyze = cell(numel(obj.TasksToRun), 1);
            if obj.IsRunScript
                functionToRun = @parallel.internal.cluster.executeScript;
                % Always 1 arg out for executeScript
                numArgsOut = 1;
                for ii = 1:numel(obj.TasksToRun)
                    scriptName = obj.TasksToRun(ii).ScriptName;
                    workspace = obj.TasksToRun(ii).WorkspaceIn;
                    allArgsIn{ii} = {scriptName, workspace, parallel.internal.apishared.PathInfo({obj.WorkerCWD}, {obj.WorkerCWD})};
                    functionsToAnalyze{ii} = parallel.internal.apishared.whatToAnalyze(scriptName, workspace);
                end
            else
                functionToRun = @parallel.internal.cluster.executeFunction;
                numArgsOut = zeros(numel(obj.TasksToRun), 1);
                for ii = 1:numel(obj.TasksToRun)
                    taskFcn = obj.TasksToRun(ii).FunctionToRun;
                    % Make sure the nargout from executeFunction is the same as
                    % the user requested from the function to run
                    numArgsOut(ii) = obj.TasksToRun(ii).NumArgsOut;
                    allArgsIn{ii} = {taskFcn, obj.TasksToRun(ii).NumArgsOut, ...
                                     obj.TasksToRun(ii).ArgsIn, parallel.internal.apishared.PathInfo({obj.WorkerCWD}, {obj.WorkerCWD})};
                    if isa(taskFcn, 'function_handle')
                        functionsToAnalyze{ii} = ...
                            parallel.internal.apishared.AttachedFiles.convertFunctionHandleForDependencyAnalysis(taskFcn);
                    else
                        % If taskFcn is not an handle it must be a string
                        [~, found] = parallel.internal.apishared.FilenameUtils.safeWhich(taskFcn);

                        % The string must be on the MATLAB path
                        if found
                            functionsToAnalyze{ii} = {taskFcn};
                        else
                            error(message('parallel:batch:StringFunctionNotOnPath',taskFcn));
                        end
                    end
                end
            end

            % At this point functionsToAnalyze should be a cell array of cellstrs.
            % We will return a cell array that lists all the dependencies for
            % each task, there will be one row per task in the same order that
            % the tasks were given.
            taskDeps = iCalculateTaskDependencies(functionsToAnalyze, obj.AutoAttachFiles);
        end

        function errorIfVectorizedBatchNotAllowed( obj )
            if ~ obj.AllowVectorizedBatch
                ex = MException(message('parallel:convenience:VectorizedBatchNotAllowed'));
                throwAsCaller(ex);
            end
        end


        function errorToThrow = getInvalidScriptOrFunctionError( obj )
        % getInvalidScriptOrFunctionError - return the error to throw on invalid
        % script or function.
            if obj.AllowVectorizedBatch
                errorToThrow = MException(message('parallel:convenience:InvalidVectorizedBatchFunction'));
            else
                errorToThrow = MException(message('parallel:convenience:InvalidScalarBatchFunction'));
            end
        end

        function isScript = checkIfScriptSupplied(obj, scriptOrFunctionToRun, possibleNargout)
        % checkIfScriptSupplied - Is the supplied function/script to batch actually a
        % function or script?

            switch class(scriptOrFunctionToRun)
              case 'function_handle'
                % Is a single function handle
                isScript = false;
              case 'char'
                % Warn if the script is the name of a file (not a folder)
                % on the machine with the extension specified; a common
                % mistake is to call batch('myfunc.m') instead of
                % batch('myfunc'). See g1247162.
                fileExists = exist(scriptOrFunctionToRun, 'file');
                isFolder = fileExists == 7;
                [~, name, extension] = fileparts(scriptOrFunctionToRun);
                if fileExists && ~isFolder && ~isempty(extension)
                    parallel.internal.warningNoBackTrace(message('parallel:convenience:BatchScriptWithExtension', scriptOrFunctionToRun, name));
                end
                
                % Could be a function name or script name, so check if
                % the nargout value is a potential nargout
                isScript = ~iIsValidNargOutArgument(possibleNargout);
              case 'cell'
                if ~ obj.AllowVectorizedBatch
                    throw( obj.getInvalidScriptOrFunctionError() );
                end
                % Could be an array of script names, an array of function handles or
                % an array of function names

                % Get class of all input tasks to run
                taskClass = cellfun(@class, scriptOrFunctionToRun, 'uniformOutput', false);
                % All tasks to run must be function handles or chars
                if ~all(ismember(taskClass(:), {'function_handle', 'char'}))
                    throw( obj.getInvalidScriptOrFunctionError() );
                end

                % If any of the tasks are function handles, then we must be running
                % functions.  Otherwise, check if the nargout value is a potential
                % nargout.
                if any(ismember(taskClass(:), 'function_handle'))
                    isScript = false;
                    if ~iIsValidNargOutArgument(possibleNargout)
                        error(message('parallel:convenience:BatchInvalidNumOutputs'));
                    end
                else
                    isScript = ~iIsValidNargOutArgument(possibleNargout);
                end
              otherwise
                throw( obj.getInvalidScriptOrFunctionError() )
            end
        end
    end

end

% -------------------------------------------------------------------------
% iIsValidNargOutArgument
% -------------------------------------------------------------------------
% Can we interpret the supplied numArgsOut argument as the number of arguments out?
% We need it to be an array of non-negative numbers.
function OK = iIsValidNargOutArgument(numArgsOut)
    OK = true;
    if ~isnumeric(numArgsOut) || isempty(numArgsOut) || ~isreal(numArgsOut) ...
        || any(round(numArgsOut(:)) ~= numArgsOut(:)) || any(numArgsOut(:) < 0)
        OK = false;
    end
end

% -------------------------------------------------------------------------
% iIsString
% -------------------------------------------------------------------------
function OK = iIsString(str)
    OK = ischar(str) && isvector(str) && size(str, 1) == 1;
end

% -------------------------------------------------------------------------
% iIsValidPoolSize
% -------------------------------------------------------------------------
function OK = iIsValidPoolSize(val)
    if isreal(val) && ...
            isnumeric(val) && ...
            any( numel( val ) == [1 2] ) && ...
            all( val == round(val) ) && ...
            all( val >= 0 ) && ...
            isfinite( val(1) ) % min value must be finite, max can be infinite
        if numel( val ) == 2
            OK = ( val(2) >= val(1) );
        else
            OK = true;
        end
    else
        OK = false;
    end
end


% -------------------------------------------------------------------------
% iCalculateTaskDependencies
% -------------------------------------------------------------------------
function taskDeps = iCalculateTaskDependencies(functionsToAnalyze, autoAttach)
    if isempty(functionsToAnalyze)
        taskDeps = {{}};
        return
    end
    % Flatten the functions to analyze, but maintain the original
    % indices so we can return a list of all dependencies for each
    % task.
    [flattened, idx] = iFlattenCellArray(functionsToAnalyze);

    % We only want to do analysis on the unique values of the functions
    % to analyze.
    [uniqueFunctions, ~, idxUnique] = unique(flattened, 'stable');

    functionCache = parallel.internal.cluster.TaskFunctionCache;
    % Now get the dependencies for each unique function. If AutoAttach
    % is true we do a full dependency analysis, otherwise we just need
    % to resolve the batch function to an absolute path.
    if autoAttach
        dependencyFcn = @(f) iWrapCalculateAttachedFiles(f, functionCache);
    else
        dependencyFcn = @iWrapFilterToolboxAndBuiltin;
    end
    dependencies = cellfun(dependencyFcn, uniqueFunctions, 'UniformOutput', false);

    % Now that we've got the task dependencies, we want to create an array
    % that matches the non-unique flattened list of functions. So we have
    % the set of dependencies for each function.
    flattenedDeps = dependencies(idxUnique);

    % Finally we need to build the output array of dependencies with
    % one row per original task.
    taskDeps = cell(numel(functionsToAnalyze), 1);
    for ii = 1:numel(taskDeps)
        deps = [flattenedDeps{idx == ii}];
        if isempty(deps)
            taskDeps{ii} = {};
        else
            taskDeps{ii} = unique(deps, 'stable');
        end
    end
end

% -------------------------------------------------------------------------
% iWrapCalculateAttachedFiles
% -------------------------------------------------------------------------
function results = iWrapCalculateAttachedFiles(fcnStr, functionCache)
    % Cache the results from the expensive call to calculateAttachedFiles
    if ~functionCache.contains( fcnStr )
        % Resolve the function to a full path before calculating
        % dependencies, see g1112418.
        resolvedFcn = iResolveFunction( fcnStr );
        results = parallel.internal.apishared.AttachedFiles.calculateAttachedFiles( resolvedFcn );
        functionCache.put( fcnStr, results );
    else
        results = functionCache.get( fcnStr );
    end
end

% -------------------------------------------------------------------------
% iWrapFilterToolboxAndBuiltin
% -------------------------------------------------------------------------
function results = iWrapFilterToolboxAndBuiltin(fcnStr)
    resolvedFcn = iResolveFunction( fcnStr );
    results = parallel.internal.apishared.AttachedFiles.filterToolboxAndBuiltin( resolvedFcn );
end

% -------------------------------------------------------------------------
% iResolveFunction
% -------------------------------------------------------------------------
function resolved = iResolveFunction(functionToResolve)
    resolved = {parallel.internal.apishared.FilenameUtils.safeWhich( functionToResolve )};
end

% -------------------------------------------------------------------------
% iFlattenCellArray
% -------------------------------------------------------------------------
function [flattened, idx] = iFlattenCellArray(cellArray)
    % iFlattenCellArray - flatten a cell array of cells into a 1D cell array
    %
    % [flattened, idx] = iFlattenCellArray(cellArray) - Flatten cellArray
    %     and return the indices of the elements in the original cell.
    %
    % Example:
    %     Given a cell of cells: {{1,2,3}, {4,5}, {6}}
    %     flattened = {1,2,3,4,5,6}
    %     idx = {1,1,1,2,2,3};

    % The input cell array must be a row vector.
    cellArray = cellArray(:)';
    idx = cellfun(@(x, y) ones(size(x)).*y, cellArray, num2cell(1:numel(cellArray)), ...
        'UniformOutput', false);
    idx = [idx{:}];
    flattened = [cellArray{:}];
end

