www.gusucode.com > appdesigner工具箱matlab源码程序 > appdesigner/+appdesigner/+internal/+model/AppModel.m

    classdef AppModel < ...
        appdesigner.internal.model.AbstractAppDesignerModel & ...
        appdesservices.internal.interfaces.model.ParentingModel
    
    % The "Model" class of the App
    %
    % This class is responsible for managing the state of the App and
    % holding onto the App Figure Window.
    %
    %
    % Copyright 2013-2016 The MathWorks, Inc.
    
    properties
        % A handle to the figure, by default this is empty
        UIFigure = matlab.ui.Figure.empty();
        
        % Name of the AppModel, corresponds to the file name
        Name;
        
        % File location of the AppModel file
        FullFileName;
        
        CodeData;
        
        % Current running app, if value is empty, there is no running App
        RunningApp;
        
        % When App is in the process of launching, IsLaunching will be true
        % This is true from when the user initates the run to when the
        % UIFigure frame appears
        IsLaunching
        
        % App's debugging state. It will be true if the App's full filename
        % is found anywhere in the debug call stack.
        IsDebugging = false;
    end
    
    properties(Access = 'private')
        % appdesigner.internal.model.AppDesignerModel
        %
        % This AppDesignerModel is the owner of this AppModel
        %
        % This property is analogous to 'Parent'
        AppDesignerModel;
        
        % Store the running app's UIFigure CodeName. If the user changes 
        % the UIFigure CodeName while the app is running, we will close
        % the running app when the user saves the app since the MCOS object
        % of the UIFigure is no longer valid
        RunningUIFigureCodeName;
        
        % Loaded component data for the app
        CodeNameComponentMap = containers.Map();
    end
    
    methods
        function obj = AppModel(appDesignerModel, proxyView, appData)
            % Constructor for AppModel
            %
            % Inputs:
            %
            %   appDesignerModel - The appdesigner.internal.model.AppDesignerModel
            %                      that will edit this App Model
            
            % Error Checking
            narginchk(2, 3);
            
            validateattributes(appDesignerModel, ...
                {'appdesigner.internal.model.AppDesignerModel'}, ...
                {});
            
            validateattributes(proxyView, ...
                {'appdesservices.internal.peermodel.PeerNodeProxyView'}, ...
                {});
            
            % Store the AppDesignerModel and ProxyView
            obj.AppDesignerModel = appDesignerModel;
            
            % set the model's name properties
            fullFileName = proxyView.getProperty('FullFileName');
            if ~isempty(fullFileName)
                obj.FullFileName = fullFileName;
                [~, obj.Name, ~] = fileparts(fullFileName);
            else
                obj.FullFileName = '';
                obj.Name = proxyView.getProperty('Name');
            end
            
            % Store loaded app data for later using during creating
            % component models
            % Do this before controller creation because it will call
            % processClientCreatedPeerNode() from
            % DesignTimeParentingController's processProxyView(), which
            % will try to create children objects
            if nargin == 3 && ~isempty(appData)
                obj.storeLoadedAppData(appData);
            end
            
            % create the controller
            obj.createController(obj.AppDesignerModel.Controller, proxyView);
            
            % add this model as a child
            obj.AppDesignerModel.addChild(obj);
        end
        
        function delete(obj)
            % Delete figure if deleting AppModel instance
            % directly from server side, which happens in test or
            % development workflow
            % In App Designer, it will be deleted through the
            % AppController
            if ~isempty(obj.UIFigure) && isvalid(obj.UIFigure)
                delete(obj.UIFigure);
            end
        end
        
        function set.UIFigure(obj, newUIFigure)
            % Error Checking
            validateattributes(newUIFigure, ...
                {'matlab.ui.Figure'}, ...
                {});
            
            % Storage
            obj.UIFigure = newUIFigure;
        end
        
        function set.CodeData(obj, codeData)
            
            validateattributes(codeData, ...
                {'appdesigner.internal.codegeneration.model.CodeData'}, ...
                {});
            
            % Storage
            obj.CodeData = codeData;
        end
        
        function set.Name(obj, newName)
            
            if ~isvarname(newName)
                error(message('MATLAB:appdesigner:appdesigner:FileNameFailsIsVarName', newName));
            else
                obj.Name = newName;
                markPropertiesDirty(obj, 'Name');
            end
        end
        
        function set.IsLaunching(obj, status)
            
            obj.IsLaunching = status;
            markPropertiesDirty(obj, 'IsLaunching');
        end
        
        function set.IsDebugging(obj, status)
            obj.IsDebugging = status;
            markPropertiesDirty(obj, 'IsDebugging');
        end
        
        function set.FullFileName(obj, newFileName)
            obj.FullFileName = newFileName;
            markPropertiesDirty(obj, 'FullFileName');
        end
        
        function adapterClassName = getAdapterClassName(obj, adapterType)
            % return the adapter class name for the given adapter type
            adapterMap = obj.AppDesignerModel.ComponentAdapterMap;
            if ( isKey(adapterMap,adapterType) )
                adapterClassName = adapterMap(adapterType);
            else
                % if  the adapter is not found then return []
                adapterClassName = [];
            end
        end
        
        function adapterMap = getAdapterMap(obj)
            % return the adapter map
            adapterMap = obj.AppDesignerModel.ComponentAdapterMap;
        end
        
        function save(obj, fileName)
            % SAVE - fileName is the full file name with path information
            % where the item is to be saved.
            
            if nargin == 1
                fileName = obj.FullFileName;
            end
            
            [~, appName] = fileparts(fileName);
            
            % If the app is running and either the UIFigure CodeName
            % changed or the current app code contains a parsing error,
            % close the running app.
            %
            % A parsing error will prevent the MCOS auto-update to occur
            % and so if the user tries to interact with the app such as
            % executing a callback, it will not work properly and no live
            % error alert will display because the app is broken (g1249971).
            % Closing the running app will prevent the user from
            % interacting with a broken app.
            if ~isempty(obj.RunningApp)
                uiFigureCodeName = obj.getUIFigureCodeName();
                
                % Determine if the code has a parsing error
                T = mtree(strjoin(obj.CodeData.GeneratedCode, '\n'));
                
                if ~strcmp(uiFigureCodeName, obj.RunningUIFigureCodeName) || ...
                        (T.count == 1 && strcmp(T.kind(), 'ERR'))
                    
                    % Need to try/catch the delete of the running app in
                    % case there is a syntax error on a previous save that
                    % was not detected by mtree (see g1290751).
                    try
                        obj.RunningApp.delete();
                        obj.RunningApp = [];
                    catch exception
                        % Because the app can not be closed, report the
                        % error as a live error alert.
                        appController = obj.getController();
                        appController.sendErrorAlertToClient(exception, fileName);
                    end
                end
            end
            
            try
                % Let the codeModel update itself in response to a save
                % This is here to support the command line API
                obj.CodeData.GeneratedClassName = appName;
                
                % Write the AppModel to the filename
                [fullFileName] = obj.writeAppToFile(fileName);
                
                % Update model because writeAppToFile returned no errors
                obj.Name = appName;
                obj.FullFileName = fullFileName;
            catch me
                
                % Restore CodeModel state to the same as it was before the
                % save was attempted
                obj.CodeData.GeneratedClassName = obj.Name;
                
                rethrow(me);
            end
        end
        
        function [fullFileName] = writeAppToFile(obj, fileName)
            % WRITEAPPTOFILE - Validate inputs and write App to file
            [path, name, ext] = fileparts(fileName);
            
            
            if ~isvarname(name)
                error(message('MATLAB:appdesigner:appdesigner:FileNameFailsIsVarName', name));
            end
            
            if isempty(path)
                % The case of saving to the current directory
                path = cd;
            end
            
            % Check if directory exists
            [success, dirAttrib] = fileattrib(path);
            
            % Directory should exist
            if ~success
                error(message('MATLAB:appdesigner:appdesigner:NotWritableLocation', fileName))
            end
            
            % Reassemble fullFileName in case the path has changed.
            fullFileName = fullfile(path, [name, ext]);
            if dirAttrib.directory && (numel(path) < numel(dirAttrib.Name))
                % if path was a relative path, path will not be the same as
                % the Name as returned by FILEATTRIB
                fullFileName = fullfile(dirAttrib.Name, [name, ext]);
            end
             
            % Get the metaData object to be serialized
            appMetadata = obj.getGroupMetaData();

 			% create the AppObject to be serialized containing the figure,
            % codeData and metadata
            appData = appdesigner.internal.serialization.app.AppData(obj.UIFigure,  obj.CodeData, appMetadata);
            
            % Write MATLAB code and AppDesigner specific data to file
            fileWriter = ...
                appdesigner.internal.serialization.FileWriter(fullFileName);
            
            % Write MATLAB code and AppDesigner specific data to
            % file
            codeForMlappFile = strjoin(appData.CodeData.GeneratedCode', '\n');
            fileWriter.writeMATLABCodeText(codeForMlappFile);
            
            % update code data with the saved generated code
            obj.CodeData.AppFileCode = codeForMlappFile;
            
            % write the appData to the file
            fileWriter.writeAppDesignerData(appData);
            
            % Check if file was created
            if exist(fileWriter.FileName, 'file') ~= 2
                error(message('MATLAB:appdesigner:appdesigner:SaveFailed', fileWriter.FileName));
            end
            
            % Only re-sync the breakpoints when performing a save (the
            % current filename is the same as the target filename). On a
            % Save As, it is not necessary to re-sync the breakpoints and
            % it can cause breakpoints not to be cleared when the Save As
            % is used to overwrite an existing app (g1078401).
            if strcmp(obj.FullFileName, fileWriter.FileName)
                try
                    % This try-catch is necessary because it is only required
                    % if the client is up and running and the breakpoint
                    % services are running.
                    % Re-sync breakpoints upon save of file
                    bpms = com.mathworks.mde.editor.plugins.matlab.MatlabBreakpointMessageService.getInstance();
                    bpms.synchronizeBreakpoints(java.io.File(fileWriter.FileName))
                catch
                end
            end
            % Clear the class
            clear(name);
        end
        
        function runApp(obj, fullFileName)
            
            % Run the App as if by command line
            [~, appName, appExt] = fileparts(fullFileName);
            
            % Silently save the app if it no longer exists because the user
            % deleted or renamed it using the file system prior to running
            % the app from App Designer.
            if exist(fullFileName, 'file') ~= 2
                try
                    save(obj, fullFileName);
                catch
                    % Saved failed and so display runtime error alert
                    exception = MException(...
                        message('MATLAB:appdesigner:appdesigner:RunFailedFileNotFound', [appName appExt], fullFileName));
                    appController = obj.getController();
                    appController.sendErrorAlertToClient(exception, fullFileName);
                    obj.IsLaunching = false;
                    return;
                end
            end
            
            funcHandle = @()appdesigner.internal.model.AppModel.runAppHelper(obj);
            
            % This is being used to defer the eval call to MATLAB until
            % after the fevals produced by the synchronization effort have
            % been complete.  This bumps the eval that creates the App to
            % the bottom of the list.
            appdesigner.internal.serialization.defer(funcHandle);
        end
        
        function component = popComponent(obj, codeName)
            component = [];
            
            if ~isempty(obj.CodeNameComponentMap) && ...
                    obj.CodeNameComponentMap.isKey(codeName)
                component = obj.CodeNameComponentMap(codeName);
                
                % After retrieving the component, remove it from the map
                % because it has been created successfully when loading the
                % app
                obj.CodeNameComponentMap.remove(codeName);
            end
            
        end
        
        function openPackageApp(obj, fullFileName)
            
            % Launch the Package App dialog for the specified .mlapp file.
            
            % Search for .prj project files in the same directory, and which
            % specify the .mlapp file as the Main File. If multiple such
            % .prj files are found, use the most recently modified. Launch
            % the Package App dialog with the most recent matching .prj file.
            % If no matching .prj file is found, open a new Package App
            % dialog, with some fields pre-populated from the .mlapp.
            
            [filePath, mlappFile, ext] = fileparts(fullFileName);
            
            aps = com.mathworks.toolbox.apps.services.AppsPackagingService;
            
            % Only call doesProjectContainMainFile() if that jar method has
            % reached this cluster. When updates reach all clusters, this
            % try block will be replaced with:
            %    mostRecentPrjFullFileName = getMostRecentPackageProject(obj, fullFileName);
            try
                prjFullFileName = fullfile(filePath, [mlappFile '.prj']);
                % Make dummy call to see if API is available
                testForMethodExistence = aps.doesProjectContainMainFile(prjFullFileName, fullFileName);
                % If no UndefinedFunction exception, it is safe to use that API method
                mostRecentPrjFullFileName = getMostRecentPackageProject(obj, fullFileName);
            catch ex
                if strcmp(ex.identifier, 'MATLAB:UndefinedFunction')
                    % Use fallback technique to get most recently modified .prj.
                    mostRecentPrjFullFileName = getMostRecentPackageProjectUsingXML(obj, fullFileName);
                else
                    % doesProjectContainMainFile() method is safe to call.
                    % It may throw an exception other than UndefinedFunction, but let it.
                    mostRecentPrjFullFileName = getMostRecentPackageProject(obj, fullFileName);
                end
            end
            
            if ~isempty(mostRecentPrjFullFileName)
                try
                    matlab.apputil.create(mostRecentPrjFullFileName); % does not do dependency checking
                catch ex
                    % Note: if .prj is in a read-only directory, user will
                    % not get an error message until an edit is made to any
                    % field in the Package App dialog. The error message
                    % will be generated by the Package App dialog.
                    if strcmp(ex.identifier, 'MATLAB:apputil:create:filenotfound')
                        error(message('MATLAB:appdesigner:appdesigner:PackageAppPackageFileNotFound', fullFileName));
                    else
                        % Unknown error using packaging API -> return generic PackageError
                        error(message('MATLAB:appdesigner:appdesigner:PackageAppFailed', fullFileName));
                    end
                end
            else
                % No .prj file found for this .mlapp file
                
                % Create new .prj file
                
                % Note: due to packaging service restrictions, the
                % packaging tool will use App Name as .prj filename.
                % For now, this should be valid as a filename, but the
                % service will strip out illegal characters later (below).
                prjFilename = obj.UIFigure.Name;
                
                % If user has not changed the title of their app from
                % the default ('UI Figure') then use the .mlapp file
                % basename instead. This will show up in the "App Name"
                % field of the Package App dialog window.
                if strcmp(prjFilename, 'UI Figure')
                    prjFilename = mlappFile;
                end
                
                originalPrjFileName = prjFilename;
                foundUniquePrjName = false;
                uniqueCounter = 0;
                while ~foundUniquePrjName
                    try
                        % .createAppsProject throws exception if filename conflict encountered
                        key = aps.createAppsProject(filePath, prjFilename);
                        
                        % The packaging API will strip illegal characters out of .prj
                        % filename, so API must be queried to determine what was used.
                        actualPrjFullFileName = aps.getProjectFileLocation(key);
                        foundUniquePrjName = true;
                    catch ex
                        if (strcmp(ex.identifier,'MATLAB:Java:GenericException') && ...
                                isa(ex.ExceptionObject, 'com.mathworks.deployment.services.NameCollisionException'))
                            % Name conflict with another .prj file. Append '_<int>' to name.
                            % Loop until a unique filename is found (e.g. App1_3.prj').
                            uniqueCounter = uniqueCounter + 1;
                            prjFilename = [originalPrjFileName '_' num2str(uniqueCounter)];
                        elseif (strcmp(ex.identifier,'MATLAB:Java:GenericException') && ...
                                isa(ex.ExceptionObject, 'java.io.FileNotFoundException'))
                            error(message('MATLAB:appdesigner:appdesigner:PackageAppPackageFileNotFound', fullFileName));
                        elseif (strcmp(ex.identifier,'MATLAB:Java:GenericException') && ...
                                isa(ex.ExceptionObject, 'com.mathworks.deployment.services.ReadOnlyException'))
                            error(message('MATLAB:appdesigner:appdesigner:PackageAppFolderNotWritable', fullFileName));
                        else
                            % Unknown error using packaging API
                            error(message('MATLAB:appdesigner:appdesigner:PackageAppFailed', fullFileName));
                        end
                    end
                end
                
                % Save the .mlappinstall to the same directory as the .mlapp file.
                aps.setOutputFolder(key, filePath);
                
                % Specify the .mlapp as the main file
                aps.addMainFile(key, fullFileName);
                
                aps.closeProject(key);
                aps.openProjectInGUIandRunAnalysis(actualPrjFullFileName); % open dialog (starts dependency checking)
            end
        end
    end
    
    methods (Access = private)
        function uiFigureCodeName = getUIFigureCodeName(obj)
            uiFigureCodeName = obj.UIFigure.DesignTimeProperties.CodeName;
        end               
        
        function mostRecentPrjFullFileName = getMostRecentPackageProject(obj, mlappFullFileName)
            % Find most recent .prj file in the same directory as the .mlapp file,
            % which has the Main File field set to the specified mlapp file.
            
            [filePath, mlappFile, ext] = fileparts(mlappFullFileName);
            
            % Find all .prj files in the same directory as the .mlapp file
            % Returns struct array with name and datenum (double) fields
            
            prjFiles = dir(fullfile(filePath, '*.prj'));
            
            aps = com.mathworks.toolbox.apps.services.AppsPackagingService;
            
            mostRecentPrjFullFileName = [];
            mostRecentPrjFileDatenum = 0;
            for file = prjFiles'
                if file.isdir
                    continue;
                end
                try
                    prjFullFileName = fullfile(filePath, file.name);
                    if aps.doesProjectContainMainFile(prjFullFileName, mlappFullFileName)
                        if file.datenum > mostRecentPrjFileDatenum
                            mostRecentPrjFullFileName = fullfile(filePath, file.name);
                            mostRecentPrjFileDatenum = file.datenum;
                        end
                    end
                catch ex
                    % Unknown error using packaging API -> return generic PackageError
                    error(message('MATLAB:appdesigner:appdesigner:PackageAppFailed', mlappFullFileName));
                end
            end
        end
        
        % Temporary code to find most recent .prj file by directly reading
        % XML in each .prj file.
        % This code will be deleted when new jar with doesProjectContainMainFile()
        % is accepted in BaT and available.
        function mostRecentPrjFullFileName = getMostRecentPackageProjectUsingXML(obj, fullMlappFileName)
            % Find most recent of the .prj files that have their Main File
            % set to the mlapp file being packaged.
            
            [filePath, mlappFile, ext] = fileparts(fullMlappFileName);
            
            % Find all .prj files in the same directory as the .mlapp file
            % Returns struct array with name and datenum (double) fields
            
            prjFiles = dir(fullfile(filePath, '*.prj'));
            
            mostRecentPrjFullFileName = [];
            mostRecentPrjFileDatenum = 0;
            for file = prjFiles'
                if file.isdir
                    continue;
                end
                prjFullFileName = fullfile(filePath, file.name);
                [filePath, fileNameSansExtension, prjExt] = fileparts(prjFullFileName);
                try
                    % The XML in the .prj file contains e.g.:
                    %   <fileset.main>
                    %       <file>${PROJECT_ROOT}\App1.mlapp</file>
                    %   </fileset.main>
                    
                    % Extract Main File field from XML in .prj
                    xDoc = xmlread(prjFullFileName);
                    filesetMainElts = xDoc.getElementsByTagName('fileset.main');
                    if filesetMainElts.getLength == 0
                        continue;
                    end
                    mainFileElt = filesetMainElts.item(0).getElementsByTagName('file');
                    if mainFileElt.getLength == 0
                        continue;
                    end
                    mainFileName = char(mainFileElt.item(0).getFirstChild.getData);
                    if isempty(mainFileName)
                        continue;
                    end
                    
                    % Strip off path component from main file name
                    if ispc
                        slashes = strfind(mainFileName, '\');
                    else
                        slashes = strfind(mainFileName, '/');
                    end
                    if ~isempty(slashes)
                        idx = slashes(end) + 1;
                        mainFileName = mainFileName(idx:end);
                    end
                    
                    % See if the Main File for this .prj matches the .mlapp
                    % file name for the app being packaged. If so, save
                    % the .prj file name if it is the most recently
                    % modified .prj file found so far.
                    if strcmp(mainFileName, [mlappFile, ext])
                        if file.datenum > mostRecentPrjFileDatenum
                            mostRecentPrjFullFileName = fullfile(filePath, file.name);
                            mostRecentPrjFileDatenum = file.datenum;
                        end
                    end
                catch ex
                    % Unknown error using packaging API -> return generic PackageError
                    error(message('MATLAB:appdesigner:appdesigner:PackageAppFailed', fullMlappFileName));
                end
            end
        end
    end
    
    methods (Static)
        
        function runAppHelper(currentAppModel)
            
            appController = currentAppModel.getController();
            appFullFileName = currentAppModel.FullFileName;
            
            % Guarentee IsLaunching is set to false and restoring current
            % folder
            isLaunchingCleanup = onCleanup(@()set(currentAppModel, 'IsLaunching', false));
            
            % Delete existing instance of the current app.
            % This IF statement has been moved from the runApp method to
            % this defered method to resolve an issue with the Run button
            % being disabled when the code has a syntax error and the
            % RunningApp instance is deleted (see g1098581).
            if(~isempty(currentAppModel.RunningApp))
                try
                    % The deletion of the previously running app could
                    % throw an exception if the running app's code was
                    % updated and fails when parsed.
                    currentAppModel.RunningApp.delete();
                    currentAppModel.RunningApp = [];
                catch
                    % Allow the exception to pass through because it will
                    % also fail when we attempt to eval the app in the code
                    % below. Reporting the eval failure is more relevant
                    % and useful than reporting the delete failure.
                end
            end
            
            % Save the current UIFigure CodeName for next saving
            % to decide if needed to close the running app or not by
            % comparing the old and new CodeName
            currentAppModel.RunningUIFigureCodeName = currentAppModel.getUIFigureCodeName();
            
            % Listen for run time errors that occur in the running
            % app's callbacks
            appController.addErrorAlertListener(currentAppModel);
            
            try
                % Store newly generated app in the obj.RunningApp. This
                % line could throw an exception if the app's code fails
                % in parsing or an error occurs in the app's constructor.
                currentAppModel.RunningApp = ...
                    appdesigner.internal.service.AppManagementService.instance().runApp(appFullFileName);
            catch exception
                if isa(exception, 'appdesigner.internal.appalert.CallbackException')
                    % Exception in startup function from app's constructor,
                    % but app already created
                    currentAppModel.RunningApp = exception.App;
                end
                rethrow(exception);
            end
            
        end
    end
    
    methods(Access = 'private')
        
        function metaData = getGroupMetaData(obj)
            groupMetaData = {};
            
            % This group meta data is neede on Save only
            % Get group manager peer node
            groupManager = [];
            peerNodes = obj.Controller.ProxyView.PeerNode.getChildren().toArray();
            for index = 1:numel(peerNodes)
                if strcmp('GroupsManager', char(peerNodes(index).getType()))
                    groupManager = peerNodes(index);
                    break;
                end
            end
            
            if ~isempty(groupManager)
                % There are groups in the app
                groups = groupManager.getChildren().toArray();
                
                for index = 1:numel(groups)
                    group = groups(index);
                    groupMetaData{end+1} = struct('Id', char(group.getId()), ...
                        'ParentGroupId', char(group.getProperty('GroupId')));
                end
            end
            
            % create the object that will be serialized
            metaData = appdesigner.internal.serialization.app.AppMetadata(groupMetaData);
        end
        
        function storeLoadedAppData(obj, appData)
            % Store the loaded app data
            
            % Flat all components to store as a map with CodeName as key
            % for quick retrieving when creating design time comopnent
            % models
            childrenList = appdesigner.internal.application.getDescendants(appData.UIFigure);
            
            for ix = 1:numel(childrenList)
                codeName = childrenList(ix).DesignTimeProperties.CodeName;
                obj.CodeNameComponentMap(codeName) = childrenList(ix);
            end
        end
    end
    
    methods(Access = 'public')
        
        function controller = createController(obj, parentController, proxyView)
            % Creates the controller for this Model
            controller = appdesigner.internal.controller.AppController(obj, parentController, proxyView);
        end
    end
    
end