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);
}
}