%Task Base class for all Task objects
%   Task objects define behavior and properties for task execution.
%
%   Task objects are constructed using the createTask method of a job.
%
%   parallel.Task methods:
%      cancel                - Cancel a pending or running task
%      delete                - Remove a task object from its job and memory
%      hasError              - Whether a task object generated an error
%      hasWarnings           - Whether a task object generated a warning
%      listAutoAttachedFiles - List the files that are automatically attached for this task
%      wait                  - Wait for task object to change state
%
%   parallel.Task properties:
%      CaptureDiary       - Specify whether to return diary output
%      CreateDateTime     - Date and time at which this task was created
%      Diary              - Text produced by execution of task object's function
%      Error              - Task error information
%      ErrorIdentifier    - Task error identifier
%      ErrorMessage       - Message from task error
%      FinishDateTime     - Date and time at which this task finished running
%      Function           - Function called when evaluating task
%      ID                 - Task's numeric identifier
%      InputArguments     - Input arguments to task function
%      Name               - Name of this task
%      NumOutputArguments - Number of arguments returned by task function
%      OutputArguments    - Output arguments from running Function on a worker
%      RunningDuration    - The duration the job has been running for, if it has started
%      Parent             - Job containing this task
%      StartDateTime      - Date and time at which this task started running
%      State              - Current state of task
%      UserData           - Data associated with a task object
%      Warnings           - Task warning information
%      Worker             - Object representing the worker for this task
%
%   See also parallel.Cluster, parallel.Job.

% Copyright 2011-2023 The MathWorks, Inc.

classdef Task < handle & ...
        matlab.mixin.Heterogeneous & ... % Different task subclasses may be concatenated
        parallel.internal.cluster.CustomPropDisp & ...
        parallel.internal.customattr.CustomGetSet

    % NB the only time we expect to concatenate tasks is during 'findTask' which
    % can accept a heterogeneous array of Job.

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Public, documented properties
    properties ( PCTGetSet, Transient, PCTConstraint='function|presubmission' )
        %Function Function called when evaluating task
        %   Function can be specified as a function handle or a string.
        %   (read-only after job submission)
        Function = @() 0;
    end
    properties ( PCTGetSet, Transient, PCTConstraint='positivescalar|presubmission' )
        %NumOutputArguments Number of arguments returned by task function
        %   (read-only after job submission)
        NumOutputArguments = 1;
    end
    properties ( PCTGetSet, Transient, PCTConstraint='cell|presubmission' )
        %InputArguments Input arguments to task function
        %   (read-only after job submission)
        InputArguments = {};
    end
    properties ( PCTGetSet, Transient, PCTConstraint='logicalscalar|presubmission' )
        %CaptureDiary Specify whether to return diary output
        %   The diary output is available in the Diary property of the task.
        %   (read-only after job submission)
        CaptureDiary = false;
    end
    properties ( PCTGetSet, Transient, PCTConstraint='string' )
        %Name Name of this task
        Name = '';
    end
    properties ( PCTGetSet, Transient, SetAccess = private )
        %Diary Text produced by execution of task object's function
        %   Diary output is produced only if CaptureDiary is true.
        %   (read-only)
        Diary = '';
    end
    properties ( Dependent, SetAccess = private )
        %State Current state of task
        %   A task may be in state 'pending', 'running', or 'finished'.
        %   (read-only)
        State
        %Worker Object representing the worker for this task
        %   The worker object is an instance of parallel.Worker.
        %   (read-only)
        Worker
        
        %RunningDuration How long the job has been running for
        %    (read-only)
        RunningDuration
    end
    methods
        function s = get.State( task )
            s = task.hGetStateEnum().Name;
        end
        function w = get.Worker( task )
            w = task.getWorker();
        end
        function d = get.RunningDuration( obj )
            d = parallel.internal.cluster.computeRunningDuration( ...
                obj.StartDateTime, obj.FinishDateTime);
        end
    end
    properties ( PCTGetSet, Transient, SetAccess = private, Hidden )
        StateEnum = parallel.internal.types.States.Pending; % Hidden backing for State property
        DiagnosticWarnings = matlab.internal.diagnostic.Warning.empty(0, 1); % Hidden backing for Warnings property
    end

    properties ( PCTGetSet, Transient, SetAccess = ?parallel.Job, Hidden )
        DependentFiles; % Hidden cache of auto attached files.
    end

    properties ( Hidden, SetAccess = immutable )
        Variant = parallel.internal.types.Variant.Task;
    end

    properties ( PCTGetSet, Transient, SetAccess = private )
        %Error Task error information
        %   The Error property contains an MException, which is empty if no
        %   error occurred.
        %   (read-only)
        Error           = ParallelException.empty();

        %ErrorMessage Message from task error
        %   (read-only)
        ErrorMessage    = '';

        %ErrorIdentifier Task error identifier
        %   (read-only)
        ErrorIdentifier = '';

        %CreateDateTime Date and time at which this task was created
        %   (read-only)
        CreateDateTime      = datetime.empty();

        %StartDateTime Date and time at which this task started running
        %   If this task has not started running, StartDateTime is empty.
        %   (read-only)
        StartDateTime       = datetime.empty();

        %FinishDateTime Date and time at which this task finished running
        %   If this task has not finished running, FinishDateTime is empty.
        %   (read-only)
        FinishDateTime      = datetime.empty();

        %OutputArguments Output arguments from running Function on a worker
        %   When the task is in state 'finished', if no error occurred during
        %   task evaluation, OutputArguments contains the results of evaluating
        %   the task's Function.
        %   (read-only)
        OutputArguments = {};
        
        %Warnings Task warning information
        %   The Warnings property contains a struct array, with three fields
        %   named message, identifier, and stack. These fields capture warning
        %   information issued during execution of the task. The property is
        %   empty if no warnings occurred.
        %   (read-only)
        Warnings = struct('identifier', '', 'message', '', 'stack', struct([]));
    end

    % FunctionName is used in display to prevent the entire function object
    %   from being loaded just for display, as this is potentially large due
    %   to workspace data serialized with it.
    properties ( PCTGetSet, Transient, SetAccess = private, Hidden )
        FunctionName
    end

    % non-stored properties:
    properties ( Transient )
        %UserData Data associated with a task object
        %   UserData can be used to store any data associated with a task
        %   object. This data is stored in the client MATLAB session, and is
        %   not available on the workers. UserData is not stored in a profile.
        UserData = [];
    end

    properties ( Transient, SetAccess = immutable )
        %Parent Job containing this task
        Parent = [];
        %ID Task's numeric identifier
        ID     = -1;
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Hidden properties:
    properties ( PCTGetSet, Transient, Hidden, SetAccess = private )
        CreateTime = ''; % Old char vector for when task was created
        StartTime  = ''; % Old char vector for when task started running
        FinishTime = ''; % Old char vector for when task finished running
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Hidden/Public methods
    methods ( Hidden )
        function hWarnIfNotSubmitted( obj )
            % Need to ask the job if it has been submitted
            obj.Parent.hWarnIfNotSubmitted();
        end        
        function hSetProperty( obj, propName, val )
        % Tasks have no special coupled properties, so simply plumb through to
        % hSetPropertyNoCheck. (NB not need to validate scalar properties here as
        % higher-level APIs "set()" and property access will already have done
        % this)
            obj.hSetPropertyNoCheck( propName, val );
        end
        function hCheckTimingForSet( tasks, timing, propName )
            % Task defines the State, so it is in a position to check
            % the timing.
            tasksStates = get(tasks, 'StateEnum');
            if ~iscell(tasksStates)
                tasksStates = {tasksStates};
            end
            cellfun(@(state) parallel.internal.cluster.checkTimingForSet(state, ...
                timing, propName), tasksStates);
        end
        
        function [taskPropertyMap, propNames] = hGetDisplayItems(obj, diFactory)
            % Create an empty map to hold displayable task properties
            taskPropertyMap = containers.Map('KeyType', 'char', 'ValueType', 'any');
            
            % The task base class is never displayed, so it does not
            % have a specified property display order to put into propNames
            propNames = {};
            
            % Add common task displayable items to map. Function is present in all task
            % displays, but must be gotten carefully for MJS Tasks, so it
            % is not added to the map in the base class.
            names = {...
                'ID', ...
                'State', ...
                'RunningDuration', ...
                };
            timeNames = {...
                'StartDateTime', ...
                'FinishDateTime', ...
                };
            specialNames = {...
                'Error', ...
                'Parent', ...
                'Warnings'};
            % Get all property values in a single call.
            % hGetDisplayPropertiesNoError should only be called once.
            propsToGet = [names timeNames specialNames];
            propValues = obj.hGetDisplayPropertiesNoError(propsToGet);
            propValueMap = containers.Map(propsToGet, propValues);
            
            values = diFactory.makeMultipleItems(@createDefaultItem, ...
                cellfun(@(name) propValueMap(name), names, 'UniformOutput', false));
            taskPropertyMap = [taskPropertyMap; containers.Map(names, values)];
            
            % Add datetime items to map
            timeValues = diFactory.makeMultipleItems(@createDateTimeItem, ...
                cellfun(@(name) propValueMap(name), timeNames, 'UniformOutput', false));
            taskPropertyMap = [taskPropertyMap; containers.Map(timeNames, timeValues)];
            
            % Add non-default displayable items to map
            taskPropertyMap('RunningDuration') = diFactory.createDurationItem(...
                propValueMap('RunningDuration'));

            taskPropertyMap('Parent') = diFactory.createLinkItem(...
                sprintf('Job %d', propValueMap('Parent').ID), ...
                propValueMap('Parent'));

            taskPropertyMap('Error') = diFactory.createTaskErrorItem(...
                propValueMap('Error'), propValueMap('Parent'));

            taskPropertyMap('Warnings') = diFactory.createTaskWarningsItem(...
                propValueMap('Warnings'), obj);

            % 'Separator' is not a typical name-value object. However, it
            % needs to be added to the propNames list in between property 
            % groups, so it is put into the map with the other 
            % displayable items.
            taskPropertyMap('Separator') = diFactory.createSeparator();
        end
        
        function stateEnum = hGetStateEnum(task)
            % Can be overridden in leaf classes
            stateEnum = task.hGetProperty('StateEnum');
        end
    end
    
    methods ( Hidden, Sealed )
        
        function link = hGetLink(obj, displayValue, jobMemento)
            if nargin < 3
                taskMemento = parallel.internal.display.TaskMemento.createFromTask(obj);
            else
                taskMemento = parallel.internal.display.TaskMemento.createFromTask(obj, jobMemento);
            end
            link = parallel.Task.hCreateLink(displayValue, taskMemento);
        end
        
    end
    
    methods ( Hidden, Static )
        function link = hCreateLink(displayValue, taskMemento)
            % Expose link creation to allow performance optimization of
            % creating link without loading task into memory
            serializedMemento = serialize(taskMemento);
            matlabFunction = sprintf('%s(''%s'')', ...
                parallel.internal.display.Displayer.DisplayObjectFunction, serializedMemento);
            link = parallel.internal.display.HTMLDisplayType(displayValue, matlabFunction);
        end
        
    end

    % Name-changed compatibility
    properties ( Hidden, Dependent )
        Id
    end
    methods
        function Id = get.Id( task )
            Id = task.ID;
        end
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Protected/Abstract methods
    methods ( Access = protected )
        function obj = Task( parent, id )
        % Constructor simply sets immutable fields.
            obj.Parent = parent;
            obj.ID     = id;
        end
        function ok = doWait( task, varargin )
        % Default implementation of task.wait() - override if you have a more
        % efficient means.
            ok = parallel.internal.cluster.wait( task, varargin{:} );
        end
        function warnIfDiaryIncomplete( task )
            % We need to check the state of the object first to see if we need to warn
            taskProps = hGetProperty( task, {'StateEnum', 'CaptureDiary'} );
            states = taskProps(:,1);
            captureDiary = taskProps(:,2);
            for idx = 1:numel(task)
                if states{idx} == parallel.internal.types.States.Running && captureDiary{idx}
                    if parallel.internal.apishared.BatchJobMethods.isBatchJob( task(idx).Parent )
                        msg = message( 'parallel:batch:DiaryIncomplete' );
                    else
                        msg = message( 'parallel:task:DiaryIncomplete' );
                    end
                    parallel.internal.warningNoBackTrace( msg );
                end
            end
        end
    end

    methods ( Access = protected, Abstract )
        % Concrete subclasses must define how to destroy tasks.
        destroyTasks( taskOrTasks );

        % Concrete subclasses must define how to cancel tasks.
        cancelOneTask( task, cancelException );

        % Return the worker that operated on this task
        getWorker( task );       
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % protected static methods
    methods ( Access = protected, Static )
        function loadCheck( ctorNargin )
        %loadCheck if the constructor is being called with zero arguments,
        %assumption is that the task is being loaded. Therefore error.
            if ctorNargin == 0
                error( message( 'parallel:task:CannotLoadFromMatFile' ) );
            end
        end
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Static, sealed protected methods
    methods (Access = protected, Static, Sealed)
        function obj = getDefaultScalarElement() %#ok<STOUT>
            % getDefaultScalarElement is required for matlab.mixin.Heterogeneous
            % to ensure that gaps in arrays are filled in with valid objects.
            error(message('parallel:cluster:ArrayWithGapsNotAllowed', 'parallel.Task'));
        end
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Static, hidden methods
    methods (Static, Hidden)
        function hWarnAboutWarnings(warnings, messageID, messageArgs)
            narginchk(2, 3);
            if ~isempty(warnings)
                warningsList = parallel.internal.display.DisplayHelper.formatWarningsList(warnings);
                if nargin < 3
                    messageArgs = {warningsList};
                else
                    messageArgs{end+1} = warningsList;
                end
                parallel.internal.warningNoBackTrace(message(messageID, messageArgs{:}));
            end
        end
    end
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Public/documented methods.
    methods ( Sealed )
        function okout = wait( task, state, timeout )
        %WAIT Wait for task object to change state
        %    WAIT(T) blocks execution in the client session until the task
        %    identified by the task object T reaches the 'finished' state. This
        %    occurs when the task is finished processing on a remote worker.
        %
        %    WAIT(T, STATE) blocks execution in the client session until the
        %    task object T changes state to STATE. For a task object the
        %    valid states are 'running' and 'finished'.
        %
        %    OK = WAIT(T, STATE, TIMEOUT) blocks execution in the client session
        %    until the task object T changes state to STATE or until TIMEOUT
        %    seconds elapsed, whichever happens first. OK is TRUE if state has
        %    been reached or FALSE in case of a timeout.
        %
        %    If a task has previously been in state STATE, then wait will return
        %    immediately. For example, if a task in the 'finished' state is
        %    asked to WAIT(T, 'running'), then the call will return immediately.
        %
        %    Example:
        %    % Create a job object.
        %    myCluster = parcluster; % access default cluster
        %    j = createJob(myCluster);
        %    % Add a task object that generates a 10x10 random matrix.
        %    t = createTask(j, @rand, 1, {10,10});
        %    % Run the job.
        %    submit(j);
        %    % Wait until the task is finished.
        %    wait(t);
        %
        %    See also parallel.Job/Wait, parcluster.
            import parallel.internal.types.States;

            validateattributes( task, {'parallel.Task'}, {'nonempty', 'scalar'}, ...
                'wait', 'task', 1 );

            if nargin < 2
                state = States.Finished;
            else
                try
                    state = States.fromName( state );
                catch E
                    ME = MException(message('parallel:cluster:FailedToInterpretState'));
                    ME = addCause( ME, E );
                    throw( ME );
                end
                okWaitStates = [ States.Running, States.Finished ];
                okWaitState  = any( state == okWaitStates );
                if ~okWaitState
                    okNames = arrayfun( @(x) x.Name, okWaitStates, 'UniformOutput', false );
                    okNamesStr = strtrim( sprintf( '%s ', okNames{:} ) );
                    error(message('parallel:cluster:InvalidStateToWait', state.Name, okNamesStr));
                end
            end

            if nargin < 3
                timeout = Inf;
            end

            try
                dctSchedulerMessage(2, 'Waiting for %s to reach %s', ...
                    parallel.internal.logging.LogHelper.getTasksString(task), state.Name);
                ok = task.doWait( state, timeout );
                dctSchedulerMessage(2, 'Finished waiting for %s to reach %s', ...
                    parallel.internal.logging.LogHelper.getTasksString(task), state.Name);
            catch E
                throw( E );
            end
            if nargout > 0
                okout = ok;
            end
        end

        function delete( taskOrTasks, varargin )
        %DELETE  Remove a task object from its job and memory
        %    DELETE(T) removes the task object, T, from the local session, and
        %    removes the task from the cluster's JobStorageLocation.  When the
        %    task is deleted, references to it become invalid. Invalid objects
        %    should be removed from the workspace with the CLEAR command. If
        %    multiple references to an object exist in the workspace, deleting
        %    one reference to that object invalidates the remaining references
        %    to it. These remaining references should be cleared from the
        %    workspace with the CLEAR command.
        %
        %    If t is an array of task objects and one of the objects cannot be
        %    deleted, the other objects in the array will be deleted and a
        %    warning will be returned.
        %
        %    Because its data is lost when you delete an object, delete should be
        %    used after output data has been retrieved from a task object.
        %
        %    myCluster = parcluster; % access the default cluster
        %    j = createJob(myCluster, 'Name', 'myJob');
        %    t = createTask(j, @rand, 1, {10});
        %    % Delete the task object.
        %    delete(t);
        %    clear t
        %
        %    See also parallel.Cluster/createJob, parallel.Job/delete, parcluster.

        % varargin is specified to ensure we get dispatched correctly; but,
        % we don't support any additional arguments other than the magic
        % string 'invalidateOnly' which allows us to invalidate the handle
        % but not destroy the underlying data.
            narginchk( 1, 2 );
            if nargin == 2
                if ~isequal( varargin{1}, 'invalidateOnly' )
                    error(message('parallel:task:InvalidDeleteSyntax'));
                end
                delete@parallel.internal.customattr.CustomGetSet( taskOrTasks );
                return
            end
            
            try
                dctSchedulerMessage(2, 'Deleting %s', ...
                    parallel.internal.logging.LogHelper.getTasksString(taskOrTasks));
                destroyTasks( taskOrTasks );
                
                % Use the built-in delete method to invalidate the objects
                delete@parallel.internal.customattr.CustomGetSet( taskOrTasks );
            catch E
                dctSchedulerMessage( 1, ['Failed to destroy tasks using the vectorized call. ' ...
                    'Attempting to destroy tasks sequentially. Original error:\n%s'], E.getReport() );
                
                lastError = [];
                for ii = 1:numel( taskOrTasks )
                    task = taskOrTasks(ii);
                    try
                        destroyTasks( task );
                
                        % Use the built-in delete method to invalidate the objects
                        delete@parallel.internal.customattr.CustomGetSet( task );
                    catch lastError
                        dctSchedulerMessage( 1, 'Failed to destroy task ''%s''. Original error:\n%s', task.hGetSupportID(), E.getReport() );
                    end
                end
                
                if ~isempty( lastError )
                    throw( lastError );
                end
            end
        end

        function cancel( taskOrTasks, optMessage )
        %CANCEL  Cancel a pending or running task
        %    CANCEL(T) stops the task object, T, that is currently in the pending
        %    or running state. The task's State property is set to finished, and
        %    no output arguments are returned. An error message stating that the
        %    task was canceled is placed in the task object's Error
        %    property.
        %
        %    CANCEL(T,'MESSAGE') cancels the task with an additional
        %    user-specified message. This message will be added to the default
        %    cancellation message. The cancellation message is used to build the
        %    task's 'Error' property.
        %
        %    Cancelling a single task in a job allows all other tasks in the
        %    same job to complete normally.
        %
        %    If the task is already in the finished state, no action is taken.
        %
        %    Example:
        %    % Create a task and later cancel it.
        %    myCluster = parcluster; % access the default cluster
        %    j  = createJob(myCluster);
        %    t  = createTask(j, @rand, 1, {3, 4});
        %    cancel(t);
        %
        %    See also parallel.Job/cancel

            import parallel.internal.cluster.CancelException
            if nargin < 2
                exceptionBuilder = @(username) CancelException.getDefaultForTask( username );
            else
                % Convert any string inputs to character vectors
                optMessage = convertStringsToChars(optMessage);
                
                validateattributes( optMessage, {'char'}, {}, ...
                                    'cancel', 'message', 2 );
                exceptionBuilder = @(username) CancelException.getWithUserMessageForTask( username, optMessage );
            end

            dctSchedulerMessage(2, 'Cancelling %s', ...
                    parallel.internal.logging.LogHelper.getTasksString(taskOrTasks));
            for taskIdx = 1:numel( taskOrTasks )
                currentTask = taskOrTasks( taskIdx );
                try
                    cancelException = exceptionBuilder( currentTask.Parent.hGetClusterUsername() );
                    cancelOneTask( currentTask, cancelException );
                catch E
                    throw( E );
                end
            end
        end

        function listAutoAttachedFiles( task )
        %listAutoAttachedFiles List the files that are automatically attached for this task
        %   listAutoAttachedFiles(t) lists the files of the task object, t, that will be or
        %   have already been attached to this task's job, following a dependency analysis
        %   performed in the task function.
        %
        %   See also parallel.Job/listAutoAttachedFiles, parallel.Job/AttachedFiles,
        %   parallel.Job/AutoAttachFiles.
        
            validateattributes( task, {'parallel.Task'}, {'nonempty', 'scalar'}, 'listAutoAttachedFiles', 'task', 1 );
            job = task.Parent;
            if job.hShouldCalculateAttachedFiles()
                % Before job submission we always calculate the attached files, if AutoAttachFiles
                % is true.
                attachedFiles = task.hCalculateDependentFiles();
                attachedFiles = attachedFiles{1};
            else
                % Once the job is submitted the auto-attached files are cached on the task.
                % In that case we just list the cached files.
                attachedFiles = task.DependentFiles;
            end

            % Do the display of the calculated attached files.
            taskFcnStr = task.hGetUserTaskFunctionForDisplay();
            if isempty( attachedFiles )
                m = message( 'parallel:task:NoAutoAttachedFiles', taskFcnStr );
            else
                formattedFiles = parallel.internal.apishared.AttachedFiles.formatAttachedFilesForDisplay( attachedFiles );
                m = message( 'parallel:task:AutoAttachedFiles', taskFcnStr, formattedFiles );
            end
            disp( m.getString() );
        end
        
        function tf = hasError( taskOrTasks )
        %HASERROR Return whether a task object has an error
        %    TF = HASERROR(T) returns a logical indicating whether the task
        %    object, T, generated an error during evaluation. TF is a N x 1
        %    logical array, where N is the number of elements in T.
        %
        %    See also parallel.Task/Error, parallel.Job/recreate
        
            errors = get( taskOrTasks, {'Error'} );
            if isempty(errors)
                tf = logical.empty;
            else
                tf = cellfun( @(x) ~isempty( x ), errors );
            end
        end
        
        function tf = hasWarnings( taskOrTasks )
        %HASWARNINGS Return whether a task object has warnings
        %    TF = HASWARNINGS(T) returns a logical indicting whether the task
        %    object, T, generated any warnings during evaluation. TF is a N x 1
        %    logical array, where N is the number of elements in T.
        %
        %    See also parallel.Task/Warnings, parallel.Job/recreate
        
            warnings = get( taskOrTasks, {'Warnings'} );
            if isempty(warnings)
                tf = logical.empty;
            else
                tf = cellfun( @(x) ~isempty( x ), warnings );
            end
        end
    end

    methods ( Sealed, Hidden )
        function hInvalidate( taskOrTasks )
        %hInvalidate Invalidate task objects without destroying underlying data.
            delete( taskOrTasks, 'invalidateOnly' );
        end

        function taskFcnStr = hGetUserTaskFunctionForDisplay( task )
            % hGetUserTaskFunctionForDisplay Get the representation of the task in a string
            % suitable for including in display.
            
            taskFcn = task.getUserTaskFunction();

            % Convert a function handle to a string.
            if isa( taskFcn, 'function_handle' )
                taskFcnStr = func2str( taskFcn );
            else
                taskFcnStr = taskFcn;
            end
        end
    end

    methods ( Hidden )
        function hSetDependentFiles( task, dependentFiles )
            task.DependentFiles = dependentFiles;
        end
    end

    methods ( Hidden, Access = ?parallel.Job )
        function dependentFiles = hCalculateDependentFiles( tasks )
        % Helper to calculate the dependent files for the task.
            taskProps = get( tasks, {'Function', 'InputArguments'} );
            taskFcns = taskProps(:,1);
            inputArgs = taskProps(:,2);
            if ~iscell( taskFcns )
                taskFcns = {taskFcns};
                inputArgs = {inputArgs};
            end
            dependentFiles = cell( size( tasks ) );
            for ii = 1:numel( tasks )
                dependentFiles{ii} = iCalculateDependentFiles( taskFcns{ii}, inputArgs{ii} );
            end
        end

        function attachedFiles = hCalculateAndCacheDependentFiles( tasks, functionCache )
        % Helper to calculate the dependent files for the task and cache them on the task.
            
            % Task dependencies are only cached once, either here or previously by
            % the BatchHelper.
            taskProps = get( tasks, {'DependentFiles', 'Function', 'InputArguments', 'StateEnum'} );
            dependentFiles = taskProps(:,1);
            functions = taskProps(:,2);
            inputArgs = taskProps(:, 3);
            states = taskProps(:, 4);

            % Only calculate dependent files for pending tasks
            isPending = [states{:}] == parallel.internal.types.States.Pending;
            tasks = tasks(isPending);
            dependentFiles = dependentFiles(isPending);
            functions = functions(isPending);
            inputArgs = inputArgs(isPending);
                            
            attachedFiles = cell( numel( tasks ), 1 );
            setDependentFiles = false( numel( tasks ), 1 );
            for ii = 1:numel( tasks )
                if isempty( dependentFiles{ii} )
                    taskFcn = functions{ii};
                    if isa( taskFcn, 'function_handle' )
                        taskFcnStr = func2str( taskFcn );
                    else
                        taskFcnStr = taskFcn;
                    end
                    if ~functionCache.contains( taskFcnStr )
                        attachedFiles{ii} = iCalculateDependentFiles( taskFcn, inputArgs{ii} );
                        functionCache.put( taskFcnStr, attachedFiles{ii} );
                    else
                        attachedFiles{ii}  = functionCache.get( taskFcnStr );
                    end
                    setDependentFiles(ii) = ~isequal( dependentFiles{ii}, attachedFiles{ii} );
                else
                    attachedFiles{ii}  = dependentFiles{ii};
                end
            end
            % Vectorized set all task dependent files in one call
            dependentFilesToSet = attachedFiles(setDependentFiles);
            if numel(dependentFilesToSet) == 1
                hSetAllowNonPublic(tasks(setDependentFiles), 'DependentFiles', dependentFilesToSet{1});
            else
                hSetAllowNonPublic(tasks(setDependentFiles), 'DependentFiles', dependentFilesToSet);
            end
        end

    end

    methods ( Access = private )

        function taskFcn = getUserTaskFunction( task )
            % hGetUserTaskFunction Get the task function that the user supplied, including
            % batch jobs (where the actual task function is executeScript or executeFunction.)
            if parallel.internal.apishared.BatchJobMethods.isBatchJob( task.Parent )
                % If we've got a batch job the 1st input argument is the task function.
                taskFcn = task.InputArguments{1};
            else
                taskFcn = task.Function;
            end
        end
        
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % API1 compatibility layer
    properties ( Hidden, Dependent )
        CaptureCommandWindowOutput
    end
    methods
        function v = get.CaptureCommandWindowOutput( task )
            v = task.CaptureDiary;
        end
    end
    methods ( Hidden, Abstract )
        pPreTaskEvaluate( task )
    end
    methods ( Hidden )
        function varargout = pGetEvaluationData( task )
            varargout = task.hGetProperty( {...
                'Function', ...
                'NumOutputArguments', ...
                'InputArguments' } );
        end
    end
end
%#ok<*ATUNK> custom attributes

function dependentFiles = iCalculateDependentFiles( taskFcn, inputArgs )
    import parallel.internal.apishared.AttachedFiles
    % We do not get the batch function from the InputArguments here,
    % this has significant overhead when there are many tasks. For batch
    % the dependency analysis has already been performed by the BatchHelper
    % and the tasks' dependencies added to the DependentFiles.
    if isa( taskFcn, 'function_handle' )
        functionsToAnalyze = ...
            AttachedFiles.convertFunctionHandleForDependencyAnalysis( taskFcn );
    else
        functionsToAnalyze = {taskFcn};
    end
    % The functions to analyze must exist on the path of the
    % client or the workers. We perform a 'safe which' that will
    % not error if the functions do not exist on the client.
    % This gives us the full path to the task function, so it can
    % be passed to 'requirements' for dependency analysis.
    % Without this we have an ambiguity, e.g. if there is a folder
    % with the same name as the task function. See g1112418.
    files = cellfun( ...
        @parallel.internal.apishared.FilenameUtils.safeWhich, ...
        functionsToAnalyze, ...
        'UniformOutput', false );

    % Get class definition files for input arguments
    classFiles = parallel.internal.apishared.calculateClassDependencies( inputArgs );
    files = [files classFiles];

    % The parallel.Job will refuse to attach toolbox files or builtins, so let's skip them now
    % rather than passing them on for dependency analysis. See g2896646.
    files = AttachedFiles.filterToolboxAndBuiltin( files );

    dependentFiles = AttachedFiles.calculateAttachedFiles( files );
    % Make sure dependentFiles is a column vector
    dependentFiles = dependentFiles(:);
end
