% DeployedNodesCollection - Collection to use in the deployed scenario

% Copyright 2011-2020 The MathWorks, Inc.

classdef (Hidden, Sealed) DeployedNodesCollection < parallel.internal.settings.AbstractNodesCollection
    properties
        % Flag indicating which collection is used to create new nodes
        CreateTransientNodes = false;
    end

    properties (GetAccess = private, SetAccess = immutable)
        % The persisted collection that is stored in "Normal" settings.
        PersistedCollection
        % The transient collection that is thrown away when MATLAB exits.
        % This is to support the profile specified at the command line
        TransientCollection
    end
    
    methods
        function obj = DeployedNodesCollection(persistedCollection, transientCollection)
        % A DeployedNodesCollection has two Node Collections - one that is really stored
        % in Settings (persisted between MATLAB session) and one that is transient (thrown away 
        % when MATLAB exits).  The two collections must be of the same type, but are 
        % independent from one another.
        % persistedCollection - An AbstractNodesCollection.  This is the collection that 
        %                       contains is persisted using stored Settings. 
        % transientCollection - An AbstractNodesCollection.  This is the collection that 
        %                       contains the transient Settings node.  This collection must 
        %                       contain no nodes initially.

            validateattributes(persistedCollection, {'parallel.internal.settings.AbstractNodesCollection'}, {'scalar'});
            validateattributes(transientCollection, {class(persistedCollection)}, {'scalar'});            
            if ~isempty(transientCollection.getAllNames())
                error(message('parallel:settings:DeployedNodesTransientCollectionNotEmpty'));
            end
            obj.PersistedCollection = persistedCollection;
            obj.TransientCollection = transientCollection;
            
            % Add a NodeAdded listener to the persisted and transient collections to
            % ensure that our NodeAdded event is fired.
            nodeAddedHandler = @(~, evtData) obj.notify('NodeAdded', evtData);
            obj.PersistedCollection.addlistener('NodeAdded', nodeAddedHandler);
            obj.TransientCollection.addlistener('NodeAdded', nodeAddedHandler);
        end
    end

    methods % overrides of methods in AbstractNodesCollection
        function nodes = getAll(obj)
        % getAll - Get the NamedNodes that are defined in this collection
            nodes = obj.PersistedCollection.getAll();
            transientNodes = obj.TransientCollection.getAll();
            nodes = [nodes, transientNodes];
        end
        
        function node = createNew(obj, varargin)
        % createNew - Build a new NamedNode of the specified type.  
        % If CreateTransientNodes is true, then builds in the TransientCollection, 
        % otherwise builds in PersistedCollection.
            import parallel.internal.settings.DeployedNodesCollection
            
            if obj.CreateTransientNodes
                node = obj.TransientCollection.createNew(varargin{:});
            else
                node = obj.PersistedCollection.createNew(varargin{:});
            end
        end
        
        function nodeNames = getAllNames(obj)
        % getAllNames - get the (public, alias) names of all the nodes
            nodeNames = obj.PersistedCollection.getAllNames();
            transientNodeNames = obj.TransientCollection.getAllNames();
            nodeNames = [nodeNames, transientNodeNames];
        end

        function node = getFromName(obj, name)
        % getFromName - get a specific node from its name.  
        % Returns an empty node if no node with that name can be found.
            validateattributes(name, {'char'}, {'row'});
            node = obj.PersistedCollection.getFromName(name);
            if ~isempty(node)
                return;
            end
            node = obj.TransientCollection.getFromName(name);
        end

        function tf = nameExists(obj, name)
        % tf = nameExists(obj, name) - does the name exist?
            validateattributes(name, {'char'}, {'row'});
            collections = [obj.PersistedCollection, obj.TransientCollection];
            tf = arrayfun(@(x) x.nameExists(name), collections);
            tf = any(tf);
        end

        function deleteNodes(obj, names)
        % deleteNodes(obj, names) - removes the specified named
        % nodes from the cache and deletes the actual node (i.e. removes 
        % from Settings).  Does not throw errors if the names do not
        % exist.
            collections = [obj.PersistedCollection, obj.TransientCollection];
            arrayfun(@(x) x.deleteNodes(names), collections);
        end

        function deleteAllNodes(obj)
        % deleteAllNodes(obj) - deletes all the nodes from the cache
        % and removes the actual Settings nodes.
            collections = [obj.PersistedCollection, obj.TransientCollection];
            arrayfun(@(x) x.deleteAllNodes(), collections);
        end
        
        function [addedNames, origNames, haveNamesChanged] = addFromCollection(obj, otherCollection, ...
            nameSuffix, level, usedNames)
        % [addedNames, origNames, haveNamesChanged] = addFromCollection(obj, otherCollection, ...
        %   nameSuffix, level, usedNames)
        % If CreateTransientNodes is true, then the nodes from the otherCollection are added 
        % to this collection's transient collection, otherwise they are added to the persisted 
        % collection.
            if nargin < 5
                usedNames = {};
            elseif ~iscellstr(usedNames) %#ok<ISCLSTR> only support cellstr
                error(message('parallel:settings:NamedNodesCollectionAddUsedNames'));
            end

            if obj.CreateTransientNodes
                destination   = obj.TransientCollection;
                existingNames = obj.PersistedCollection.getAllNames();
            else
                destination   = obj.PersistedCollection;
                existingNames = obj.TransientCollection.getAllNames();
            end

            allUsedNames = [usedNames(:); existingNames(:)]';
            [addedNames, origNames, haveNamesChanged] = destination.addFromCollection(...
                    otherCollection, nameSuffix, level, allUsedNames);
        end 
    end
    
    methods (Access = protected)
        function settingsNames = getAllSettingsNames(obj)
            % getAllSettingsNames - get the SettingsNames of all the nodes
            settingsNames = obj.PersistedCollection.getAllSettingsNames();
            transientSettingsNames = obj.TransientCollection.getAllSettingsNames();
            settingsNames = [settingsNames, transientSettingsNames];
        end
    end
end
