% CustomGetSet - base class supporting get() and set() using CustomPropTypes.
% Subclasses of this class must still overload the abstract methods of
% CustomPropTypes:
% - val = hGetProperty( obj, propName ) % return a property by name
% - hSetProperty( obj, propName, val )  % set a property by name
% - hSetPropertyNoCheck( obj, propName, val ) % set a property by name, do not check the value
%
% Note that methods implemented by this class are Sealed to allow use with
% heterogeneous arrays.

% Copyright 2011-2021 The MathWorks, Inc.

classdef ( Hidden ) CustomGetSet < parallel.internal.customattr.CustomPropTypes

    methods ( Access = protected, Sealed )
        function checkValidUserSettablePropNames( objOrObjs, userProps )
            % Subclasses may wish to call this to see if some user-supplied
            % are public-settable on an object.
            import parallel.internal.customattr.Reflection;
            import parallel.internal.customattr.GetSetImpl;
            
            % To ensure an appropriate error message is produced, first
            % check that the properties are valid ...
            visibleProps = Reflection.getAllPublicProperties( objOrObjs );
            GetSetImpl.errorIfInvalidProps( objOrObjs, visibleProps, userProps );
            
            % ... and then check that they are user-settable
            userSettableProps = Reflection.getAllUserSettableProperties( objOrObjs );
            GetSetImpl.errorIfNonPublicSettableProps( objOrObjs, userSettableProps, userProps );
        end
    end

    methods ( Hidden, Sealed )
        function vcell = get( objOrObjs, varargin )
            import parallel.internal.customattr.GetSetImpl;
            try
                if numel( objOrObjs ) > 1
                    vcell = iHetFunGetProperty( objOrObjs, varargin{:} );
                else
                    vcell = GetSetImpl.getImpl( objOrObjs, varargin{:} );
                end
            catch E
                throw( E );
            end
        end

        function varargout = set( objOrObjs, varargin )
            import parallel.internal.customattr.GetSetImpl;
            allowNonPublic = false;
            try
                vectorizeSet = all(objOrObjs.checkVectorizedSetSupport());
                [ varargout{1:nargout} ] = GetSetImpl.setImpl( allowNonPublic, vectorizeSet, objOrObjs, varargin{:} );
            catch E
                throw( E );
            end
        end
    end

    methods
        function obj = saveobj( obj )
            parallel.internal.warnOnceCannotSave( class( obj ) );
        end
    end
    methods ( Static )
        function obj = loadobj( obj )
            parallel.internal.warningNoBackTrace( ...
                message( 'MATLAB:parallel:loadsave:CannotLoadCorrectly', ...
                          class( obj ) ) );
        end
    end

    methods ( Hidden, Sealed )
        % Template method to provide hooks for subclasses to implement.
        function [values, handled] = hVectorisedGet( obj, propsToAccess, handled, existingValues )
            [values, handled] = obj.preVectorisedGet( propsToAccess, handled, existingValues );
            [values, handled] = obj.doVectorisedGet( propsToAccess, handled, values );
        end
        
        function varargout = hSetAllowNonPublic( objOrObjs, varargin )
        % Identical to set() except that non-public set properties are also allowed.
            import parallel.internal.customattr.GetSetImpl;
            allowNonPublic = true;
            try
                vectorizeSet = all(objOrObjs.checkVectorizedSetSupport());
                [ varargout{1:nargout} ] = GetSetImpl.setImpl( allowNonPublic, vectorizeSet, objOrObjs, varargin{:} );
            catch E
                throw( E );
            end        
        end
    end
    
    methods ( Access = protected )
        function [values, handled] = preVectorisedGet( ~, ~, handled, values )
            % Default method. Do nothing.
        end

        function [values, handled] = doVectorisedGet( obj, propsToAccess, handled, existingValues )
        % Overridable method to allow classes to implement their own
        % vectorized get of non-PCTGetSet properties.
        % Only get the values for which the corresponding element of
        % handled is false.
            helperFunc = @(a,b,c) iVectorisedGetHelper(obj,a,b,c);
            [values, handled] = cellfun( helperFunc, propsToAccess, handled, existingValues, ...
                                         'UniformOutput', false );            
        end
        
        function supportVec = checkVectorizedSetSupport( ~ )
            % Flag to indicate whether this class supports vectorized sets over
            % multiple instances. By default this flag is false and sets will
            % be iterated over. Override this method in child classes which
            % support vectorized sets.
            % Classes which override this to return true must support calls to
            % hSetProperty and hSetPropertyNoCheck with multiple instances.
            supportVec = false;
        end
    end

end
%--------------------------------------------------------------------------
% iVectorisedGetHelper: helper function for bulk getting of access
% properties
function [val, handled] = iVectorisedGetHelper(obj, name, alreadyHandled, existingValue)   
    if alreadyHandled
        val = existingValue;
        handled = alreadyHandled;
    else
        if numel(obj) > 1
            val = {obj.(name)}';
        else
            val = obj.(name);
        end
        handled = true;
    end
end

%-----------------------------------------------------
function v = iHetFunGetProperty( objOrObjs, varargin )   
    props = varargin{:};
    v = parallel.internal.cluster.hetfun( @(o) iHetFunGetFunction( o, props ), objOrObjs, 'stable' );
    % This double expansion is required since both hetfun, and the
    % method we must pass to hetfun wrap the outputs in cell arrays.
    v = vertcat( v{:} );
    v = vertcat( v{:} );
end

%-----------------------------------------------------
% Get function to pass to hetfun. This ensures that each object is wrapped
% in its own cell array to fit the requirements for the 'stable' flag.
function vCell = iHetFunGetFunction( objOrObjs, varargin )
    import parallel.internal.customattr.GetSetImpl;

    nObj = numel( objOrObjs );
    vCell = cell( nObj, 1 );
    v = GetSetImpl.getImpl( objOrObjs, varargin{:} );
    if ~iscell(v)
        v = {v};
    end
    for idx = 1:nObj
        vCell{idx} = v(idx, :);
    end
end

