/*
* Copyright 2003-2015 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.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.util.InvalidDataException;
import jetbrains.mps.ide.vfs.ProjectRootListenerComponent;
import jetbrains.mps.project.persistence.ProjectDescriptorPersistence;
import jetbrains.mps.project.structure.project.ModulePath;
import jetbrains.mps.project.structure.project.ProjectDescriptor;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.MacroHelper.MacroNoHelper;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.module.SModule;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* FIXME: AP what is the difference with the MPSProject? Both are based on the idea platform... Merge?
*
* It must save/load its state only via the platform methods #saveState, #loadState
* The project may be changed externally via addModule/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> {
private static final Logger LOG = LogManager.getLogger(StandaloneMPSProject.class);
@SuppressWarnings("UnusedParameters")
public StandaloneMPSProject(final Project project, ProjectLibraryManager projectLibraryManager, ProjectRootListenerComponent unused) {
super(project, unused);
}
@Override
public Element getState() {
if (getProject().isDefault()) {
return null;
}
return new ModelAccessHelper(getModelAccess()).runReadAction(() -> {
ProjectDescriptor descriptor = getProjectDescriptor();
String presentableUrl = getProject().getPresentableUrl();
assert presentableUrl != null; // by contract the project is default <=> url == null
File projectFile = new File(presentableUrl);
return new ProjectDescriptorPersistence(projectFile, new MacroNoHelper()).save(descriptor);
});
}
@Override
public void loadState(Element state) {
LOG.info("Loading the project '" + getName() + "' from disk");
if (!getProject().isDefault()) {
if (state == null) {
throw new IllegalArgumentException("State is null");
}
loadDescriptor(new ElementProjectDataSource(state, getProjectFile()));
if (ProjectManager.getInstance().getOpenedProjects().contains(this)) {
update();
}
}
}
/**
* @deprecated remove in 3.4 and make final
*/
@NotNull
@Deprecated
public String getErrors() {
return super.getErrors();
}
@Override
public void disposeComponent() {
super.disposeComponent();
dispose();
}
@NotNull
public List<ModulePath> getAllModulePaths() {
return Collections.unmodifiableList(myProjectDescriptor.getModulePaths());
}
// todo remove; project descriptor is its internal substance which represents the persistence data
@NotNull
@ToRemove(version = 3.3)
public ProjectDescriptor getProjectDescriptor() {
return myProjectDescriptor;
}
// todo remove
@ToRemove(version = 3.3)
public void setProjectDescriptor(ProjectDescriptor projectDescriptor) {
myProjectDescriptor = projectDescriptor;
update();
}
// AP fixme : public update exposes the project internals too much (as it looks for me)
public final void update() {
ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
long beginTime = System.nanoTime();
LOG.info("Updating " + getName());
try {
if (progressIndicator != null) {
progressIndicator.setText2("Loading Project Modules");
}
super.update();
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);
}
// AP: fixme these two methods are working with the UI virtual paths; I want them to be extracted somewhere else
@Nullable
public String getFolderFor(@NotNull SModule module) {
ModulePath modulePath = getPath(module);
if (modulePath != null) {
return modulePath.getVirtualFolder();
} else {
LOG.warn("Could not find module path for the module " + module);
return null;
}
}
// XXX there's no reason to keep this method if ProjectBase#setVirtualFolder get exposed and MPS model references to this one get updated.
public void setFolderFor(@NotNull SModule module, String newFolder) {
super.setVirtualFolder(module, newFolder);
}
}