% ResourceHandle Manage lifecycle of opaque handle object that has destructor.
% Destroy opaque handle object when ResourceHandle is destroyed. Designed to
% manage shared resources acquired from external systems.
%
% See Also Connection

% Copyright 2018 The MathWorks, Inc.
classdef ResourceHandle < handle
    
    properties (Dependent)
        IsActive     % Is the Resource still usable?
    end
    
    properties (Access = private)
        Destructor   % Call this destructor when ResourceHandle deleted.
        Handle       % Opaque handle to resource data.
        EmptyMsg     % Emit when handle is unexpectedly empty.
        Validator    % If non-empty, call to validate handle
    end
    
    methods
        function h = ResourceHandle(handle, destructor, msg, validator)
        %ResourceHandle Create ResourceHandle. Three, four or zero inputs.
        %
        %   h = ResourceHandle(HANDLE, DESTRUCTOR, MSG, VALIDATOR) prevents
        %   resource leaks by destroying HANDLE with DESTRUCTOR when the
        %   ResourceHandle H is deleted. Optional VALIDATOR function
        %   determines if HANDLE remains valid and may replace HANDLE with
        %   a new handle if HANDLE is invalid.
        %
        %   Clients access HANDLE via HANDLE = get(H). get will issue 
        %   error MSG if H is empty.
        %
        %   See Also get, Connection

            h.Validator = [];

            if nargin >= 3
                h.Handle = handle;
                h.Destructor = destructor;
                h.EmptyMsg = msg;
                if nargin == 4
                    h.Validator = validator;
                end
            else
                h.Handle = [];
                h.Destructor = [];
                h.EmptyMsg = '';
            end
        end
        
        function disable(h)
        %DISABLE Destroy the opaque handle, but leave the validator and
        %destructor in place. A disabled connection may be re-established,
        %while a destroyed one may not.
        %
        %   See Also destroy, reset, Connection
        
            % Do not check IsActive property, as that  will reconnect.
            if ~isempty(h.Destructor) && ~isempty(h.Handle)
                feval(h.Destructor, h.Handle);
            end  
            h.Handle = [];        
        end
        
        function destroy(h)
        %DESTROY Destroy the opaque handle. Call the resource destructor,
        %and then forget all we knew about it.
        %
        %   See Also disable, reset, release, Connection
        
            % Call the destructor on any active handle. Omit IsActive check
            % to prevent inadvertent reconnection. Destructor must be able
            % to handle invalid connections.
            if ~isempty(h.Destructor) && ~isempty(h.Handle)
                feval(h.Destructor, h.Handle);
            end  
            release(h);
        end
        
        function handle = reset(h)
        %RESET Release and then reacquire persistence service handle. Will
        %error if reconnection not possible.
            handle = [];
            
            % Call the destructor on any active handle.
            if ~isempty(h.Destructor) && h.IsActive
                feval(h.Destructor, h.Handle);
            end 

            % Attempt to reconnect, if validator permits it.
            vf = h.Validator;
            [valid,c] = vf(0);
            if valid == false && ~isempty(c)
                h.Handle = c;
                valid = true;
            end  
            % Return new handle if reconnection succeeded.
            if valid == true
                handle = h.Handle;
                return;
            end
            
            % It's a sad day.
            error(h.EmptyMsg);
        end
        
        function handle = release(h)
        %RELEASE Stop managing opaque handle. Do not call resource destructor.
            handle = h.Handle;
            h.Handle = [];
            h.Validator = [];
            h.Destructor = [];
        end
        
        function tf = get.IsActive(h)
        % get.IsActive Call the validator to determine if the handle is
        % valid. Validators may attempt to reset (reconnect) invalid
        % handles. 
        %
        % Dependent property.
            tf = ~isempty(h.Handle);
            if tf && ~isempty(h.Validator)
                vf = h.Validator;
                tf = vf(h.Handle);
            end
        end

        function handle = get(h)
        % get Allow access to opaque handle. Issue error on invalid handle.
        % Validate handle, if validator available. This may replace an
        % invalid handle with a new, valid handle. Do not cache the handle
        % returned by get.
        %
        % Use with care. Never call get() if h.Handle is empty.
        
            valid = false;
            if ~isempty(h.Validator) 
                vf = h.Validator;
                % Validator function may return a new handle, if the
                % old one has become invalid. Don't set h.Handle to 0,
                % because the destructor doesn't like double(0) -- it
                % prefers empty.
                if isempty(h.Handle)
                    [valid,c] = vf(0);
                else
                    [valid,c] = vf(h.Handle);
                end
                if valid == false && ~isempty(c)
                    h.Handle = c;
                    valid = true;
                end
            end

            if valid
                handle = h.Handle;
                return;
            end

            error(h.EmptyMsg);
        end
        
        function delete(h)
        % delete ResourceHandle destructor. Destroy resource by calling 
        % resource destructor.
            destroy(h);
        end
            
    end
end
