/* * Copyright 2003-2016 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.ide.projectPane.logicalview.highlighting.visitor; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.DumbService; import jetbrains.mps.extapi.model.GeneratableSModel; import jetbrains.mps.generator.ModelGenerationStatusManager; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.ide.projectPane.logicalview.highlighting.visitor.updates.AdditionalTextNodeUpdate; import jetbrains.mps.ide.ui.tree.module.NamespaceTextNode; import jetbrains.mps.ide.ui.tree.module.ProjectModuleTreeNode; import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode; import jetbrains.mps.make.IMakeService; import jetbrains.mps.project.Project; import jetbrains.mps.smodel.Generator; import jetbrains.mps.smodel.Language; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.EditableSModel; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.module.SModule; import javax.swing.tree.TreeNode; public class GenStatusUpdater extends TreeUpdateVisitor { public GenStatusUpdater(Project mpsProject) { super(mpsProject); } private ProjectModuleTreeNode getContainingModuleNode(TreeNode node) { do { node = node.getParent(); if (node == null) return null; } while (!(node instanceof ProjectModuleTreeNode)); return (ProjectModuleTreeNode) node; } private boolean isTimeToRelax() { if (IMakeService.INSTANCE.isSessionActive()) { return true; } Application application = ApplicationManager.getApplication(); return (application.isDisposed() || application.isDisposeInProgress() || myProject.isDisposed()); } @Override public void visitModuleNode(@NotNull final ProjectModuleTreeNode node) { // XXX might be fruitful to have pre/post visit notifications, so that we can get rid of propagateStatusToNamespaceNodes (do it from post visit) if (node.isInitialized()) { // we've got children (SModelTreeNodes) and there's update for them in #visitModelNode(), below return; } scheduleModelRead(node, new Runnable() { @Override public void run() { if (node.getModule().isReadOnly()) { new StatusUpdate(node).update(GenerationStatus.READONLY); return; } final com.intellij.openapi.project.Project project = ProjectHelper.toIdeaProject(myProject); if (project != null && DumbService.getInstance(project).isDumb()) { // see visitModelNode for explanation propagateStatusToNamespaceNodes(node, GenerationStatus.UPDATING); return; } GenerationStatus s = new StatusUpdate(node).update(); // no need to check for generator and language here as #visitModelNode does, as now // we can face generator module only as sibling to language's models (i.e. SModelTreeNodes) propagateStatusToNamespaceNodes(node, s); } }); } @Override public void visitModelNode(@NotNull final SModelTreeNode modelNode) { scheduleModelRead(modelNode, new Runnable() { @Override public void run() { if (isTimeToRelax()) { return; } SModel md = modelNode.getModel(); if (!(md instanceof EditableSModel)) { return; } if (!(md instanceof GeneratableSModel)) { return; } if (md.getModule() == null) return; boolean wasChanged = ((EditableSModel) md).isChanged(); if (!wasChanged && !((GeneratableSModel) md).isGeneratable()) { // changing doNotGenerate := true immediately renders the model notGeneratable // while GenStatusUpdater needs to update its status return; } final ProjectModuleTreeNode moduleNode = getContainingModuleNode(modelNode); if (moduleNode == null) { return; } if (moduleNode.getModule().isReadOnly()) { new StatusUpdate(modelNode).update(GenerationStatus.READONLY); new StatusUpdate(moduleNode).update(GenerationStatus.READONLY); return; } final com.intellij.openapi.project.Project project = ProjectHelper.toIdeaProject(myProject); if (project != null && DumbService.getInstance(project).isDumb()) { // while idea updates its index, we can't use index to check model hashes. // of course, we can calculate hash again (i.e. if none in index found), // however, as long as we use index for hashes, seems reasonable to wait for end of dumb mode // and to update status again then (PPTH.dumbUpdate does that). // Here, I don't care to set status of individual models and modules - status for a group seems to be enough propagateStatusToNamespaceNodes(moduleNode, GenerationStatus.UPDATING); return; } new StatusUpdate(modelNode).update(); GenerationStatus s = new StatusUpdate(moduleNode).update(); if (moduleNode.getModule() instanceof Generator) { final ProjectModuleTreeNode languageNode = getContainingModuleNode(moduleNode); if (languageNode != null) { new StatusUpdate(languageNode).update(s); } } propagateStatusToNamespaceNodes(moduleNode, s); } }); } private void propagateStatusToNamespaceNodes(ProjectModuleTreeNode node, GenerationStatus status) { final AdditionalTextNodeUpdate r = new AdditionalTextNodeUpdate(status.getMessage()); for (TreeNode n = node; n != null; n = n.getParent()) { if (n instanceof NamespaceTextNode) { addUpdate((NamespaceTextNode) n, r); } } } private class StatusUpdate { private final SModelTreeNode myModelNode; private final ProjectModuleTreeNode myModuleNode; StatusUpdate(ProjectModuleTreeNode moduleNode) { myModuleNode = moduleNode; myModelNode = null; } StatusUpdate(SModelTreeNode modelNode) { myModuleNode = null; myModelNode = modelNode; } public GenerationStatus update() { if (myModuleNode == null && myModelNode == null) { return null; } // FIXME update is inside model read already, no need to wrap once again GenerationStatus status = compute(); update(status); return status; } public void update(GenerationStatus status) { if (myModelNode != null) { addUpdate(myModelNode, new AdditionalTextNodeUpdate(status.getMessage())); } if (myModuleNode != null) { addUpdate(myModuleNode, new AdditionalTextNodeUpdate(status.getMessage())); } } private GenerationStatus compute() { if (myModelNode != null) { // extra check before read action if (myModelNode.getModel().getModule() == null) { return GenerationStatus.NOT_REQUIRED; } return getGenerationStatus(myModelNode); } if (myModuleNode != null) { return getGenerationStatus(myModuleNode); } throw new IllegalStateException(); } } private static boolean generationRequired(SModule module) { for (SModel md : module.getModels()) { if (ModelGenerationStatusManager.getInstance().generationRequired(md)) { return true; } } return false; } static GenerationStatus getGenerationStatus(ProjectModuleTreeNode node) { SModule module = node.getModule(); if (module.isReadOnly()) { return GenerationStatus.READONLY; } if (generationRequired(module)) return GenerationStatus.REQUIRED; if (module instanceof Language) { for (Generator generator : ((Language) module).getGenerators()) { if (generationRequired(generator)) return GenerationStatus.REQUIRED; } } return GenerationStatus.NOT_REQUIRED; } private static GenerationStatus getGenerationStatus(SModelTreeNode node) { if (node.getModel() == null) return GenerationStatus.NOT_REQUIRED; if (isPackaged(node)) return GenerationStatus.READONLY; if (isDoNotGenerate(node)) return GenerationStatus.DO_NOT_GENERATE; boolean required = ModelGenerationStatusManager.getInstance().generationRequired(node.getModel()); return required ? GenerationStatus.REQUIRED : GenerationStatus.NOT_REQUIRED; } private static boolean isPackaged(SModelTreeNode node) { SModel md = node.getModel(); if (!(md instanceof EditableSModel)) return false; return md.isReadOnly(); } private static boolean isDoNotGenerate(SModelTreeNode node) { SModel md = node.getModel(); if (!(md instanceof GeneratableSModel)) return false; return ((GeneratableSModel) md).isDoNotGenerate(); } public static enum GenerationStatus { READONLY("read only"), DO_NOT_GENERATE("do not generate"), UPDATING("updating..."), REQUIRED("generation required"), NOT_REQUIRED(null); private String myMessage; GenerationStatus(String message) { myMessage = message; } @Nullable public String getMessage() { return myMessage; } } }