workbench/mps-workbench/source/jetbrains/mps/project/StandaloneMPSProject.java (130 lines of code) (raw):

/* * Copyright 2003-2024 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.project; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectCloseListener; import com.intellij.openapi.util.InvalidDataException; import com.intellij.platform.backend.workspace.GlobalWorkspaceModelCache; import com.intellij.platform.backend.workspace.WorkspaceModel; import com.intellij.util.messages.MessageBusConnection; import jetbrains.mps.ide.MPSCoreComponents; import jetbrains.mps.ide.util.MPSProjectActivity; import jetbrains.mps.logging.Logger; import jetbrains.mps.project.persistence.ProjectDescriptorPersistence; import jetbrains.mps.project.structure.project.ProjectDescriptor; import jetbrains.mps.project.structure.project.ProjectDescriptor.Builder; import jetbrains.mps.smodel.RepoListenerRegistrar; import jetbrains.mps.util.MacrosFactory; import jetbrains.mps.vfs.IFile; import jetbrains.mps.vfs.VFSManager; import jetbrains.mps.vfs.tracking.ModelStorageProblemsListener; import kotlin.Unit; import org.jdom.Element; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; import java.io.IOException; /** * This class is for MPS as a standalone IDE, while MPSProject is in use in MPS as IDEA plugin. * Note for AP from MM: at least I've added a difference in how module file changes are handled. * * It must save/load its state only via the platform methods #saveState, #loadState * The project may be changed externally via addModuleEntry/removeModule methods, * * ProjectDescriptor of the Project is supposed to be always in sync with the project state. * * evgeny, 11/10/11 */ @State( name = "MPSProject", storages = @Storage("modules.xml") ) public class StandaloneMPSProject extends MPSProject implements PersistentStateComponent<Element> { public static class Activity extends MPSProjectActivity { @Override public void runActivity(@NotNull Project project) { project.getService(MPSProject.class).projectOpened(); } } public static class Listener implements ProjectCloseListener { @Override public void projectClosed(@NotNull Project p) { p.getService(MPSProject.class).projectClosed(); } } private static final Logger LOG = Logger.getLogger(StandaloneMPSProject.class); private ModuleFileChangeListener myListener; private final VFSManager myManager; private final ModelStorageProblemsListener myProblemsListener; // AP fixme must be final, however StandaloneMpsProject exposes it (a client can publicly reset the project descriptor) private ProjectDescriptor myProjectDescriptor; @SuppressWarnings("UnusedParameters") public StandaloneMPSProject(final Project project) { super(project); myProjectDescriptor = null; // we used to have ProjectLibraryManager in dependencies to ensure project libraries are ready, // but now project libraries get initialized from a lifecycle listener, and I see no point to care to init PLM here. // The dependency was introduced in db00760f. Proper dispose order is ensured by the listener now. myManager = MPSCoreComponents.getInstance().getPlatform().findComponent(VFSManager.class); myListener = new ModuleFileChangeListener(this); addListener(myListener); // NOTE, this listener used to be installed with ModelTracking project component both for Big MPS and MPS-as-IDEA plugin. // With ProjectComponents eclipse, moved listener registration here explicitly. For completeness, same registration has // to be part of MPS-as-IDEA-plugin project init sequence (likely, in MPSFacet or directly by MPSProject), but as long as // MPS-as-IDEA-plugin is now a history, *AND* this listener is relevant for IDE/UI activities only (not for headless IDEA-backed // environment where we still can get simple MPSProject instance), I decided to put it here. // FWIW, could be ProjectRepository attribute, not a listener. See conflict resolver in superclass myProblemsListener = new ModelStorageProblemsListener(this); } @Override public Element getState() { if (getProject().isDefault()) { return null; } ProjectDescriptor descriptor = getProjectDescriptor(); // getProjectFile() uses ideaProject.getPresentableUrl. By contract the project is default <=> presentable url == null IFile projectFile = myManager.getFileSystem(VFSManager.FILE_FS).getFile(getProjectFile()); // IDEA expands $PROJECT_DIR$ for us in loadState, but here we need to give paths with the right macro, and // MacrosFactory.forProjectFile does this. return new ProjectDescriptorPersistence(projectFile, MacrosFactory.forProjectFile(projectFile)).save(descriptor); } @Override public void loadState(@NotNull Element state) { LOG.info("Loading the project '" + getName() + "' from disk"); if (!getProject().isDefault()) { IFile projectFile = myManager.getFileSystem(VFSManager.FILE_FS).getFile(getProjectFile()); // here, global macro helper is ok, as it's IDEA's responsibility to expand $PROJECT_DIR$ in modules.xml myProjectDescriptor = new ProjectDescriptorPersistence(projectFile, MacrosFactory.getGlobal()).load(state); update(); } } @Override public void projectOpened() { super.projectOpened(); new RepoListenerRegistrar(getRepository(), myProblemsListener).attach(); } @Override public void projectClosed() { new RepoListenerRegistrar(getRepository(), myProblemsListener).detach(); removeListener(myListener); super.projectClosed(); } // todo remove; project descriptor is its internal substance which represents the persistence data @NotNull @Deprecated(since = "3.3", forRemoval = true) public ProjectDescriptor getProjectDescriptor() { Builder builder = new Builder(getName()); // read access here is just a way to guard myModuleLoader (in allModulePaths) changes from a write action (e.g. update()) // perhaps, shall introduce a separate lock, rather than use MA. getModelAccess().runReadAction(() -> { forEachModuleEntry(builder::addModuleEntry); }); return builder.build(); } // todo remove @Deprecated(since = "3.3", forRemoval = true) public void setProjectDescriptor(ProjectDescriptor projectDescriptor) { myProjectDescriptor = projectDescriptor; update(); } // AP fixme : public update exposes the project internals too much (as it looks for me) @Override public final void update() { if (myProjectDescriptor == null) { // nothing to update return; } ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); long beginTime = System.nanoTime(); LOG.info("Updating " + getName()); try { if (progressIndicator != null) { progressIndicator.setText2("Loading project modules"); } getModelAccess().runWriteAction(() -> { reloadProject(myProjectDescriptor); myProjectDescriptor = null; // indicate it's all in RT now. }); if (progressIndicator != null) { progressIndicator.setText2(""); } } finally { LOG.info(String.format("Updating %s took %.3f s", getName(), (System.nanoTime() - beginTime) / 1e9)); } } public static StandaloneMPSProject open(@NotNull String projectPath) throws JDOMException, InvalidDataException, IOException { return (StandaloneMPSProject) MPSProject.open(projectPath); } }