/* * 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.listeners; import jetbrains.mps.generator.ModelGenerationStatusListener; import jetbrains.mps.generator.ModelGenerationStatusManager; import jetbrains.mps.ide.projectPane.logicalview.PresentationUpdater; import jetbrains.mps.ide.projectPane.logicalview.highlighting.visitor.TreeUpdateVisitor; import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode; import jetbrains.mps.smodel.RepoListenerRegistrar; import jetbrains.mps.smodel.SModelAdapter; import jetbrains.mps.smodel.SModelInternal; import jetbrains.mps.smodel.loading.ModelLoadingState; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.module.SRepositoryContentAdapter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Control listeners that track changes to a model node. * Invoke {@link #startListening(SRepository)}/{@link #stopListening(SRepository)} to enable/disable listening, * and {@link #attach(jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode)}/{@link #detach(jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode)} to \ * include/exclude selected model tree node from update. */ public class SModelNodeListeners { private final ModelChangeListener myModelChangeListener; private final SRepositoryContentAdapter myRepositoryListener; private final GenStatusTracker myGenStatusListener; /** * There might be more than one tree node for the same model (e.g. one under language, another under @descriptor), * we need to track all tree nodes to update them on model change */ private final Map<SModelReference, Collection<SModelTreeNode>> myTreeNodes = new HashMap<SModelReference, Collection<SModelTreeNode>>(); private final TreeUpdateVisitor[] myUpdates; // shall be CompositeVisitor, but I'm lazy for that public SModelNodeListeners(TreeUpdateVisitor genStatusUpdate, TreeUpdateVisitor errorVisitor, TreeUpdateVisitor modifiedMarker) { myUpdates = new TreeUpdateVisitor[3]; myUpdates[0] = genStatusUpdate; myUpdates[1] = errorVisitor; myUpdates[2] = modifiedMarker; myModelChangeListener = new ModelChangeListener(); myRepositoryListener = new SRepositoryContentAdapter() { @Override protected void startListening(SModel model) { model.addModelListener(this); } @Override protected void stopListening(SModel model) { model.removeModelListener(this); } @Override public void modelReplaced(SModel model) { refreshAffectedTreeNodes(model); } }; myGenStatusListener = new GenStatusTracker(genStatusUpdate); } public void startListening(SRepository projectRepository) { new RepoListenerRegistrar(projectRepository, myRepositoryListener).attach(); ModelGenerationStatusManager.getInstance().addGenerationStatusListener(myGenStatusListener); } public void stopListening(SRepository projectRepository) { ModelGenerationStatusManager.getInstance().removeGenerationStatusListener(myGenStatusListener); new RepoListenerRegistrar(projectRepository, myRepositoryListener).detach(); } public void attach(@NotNull SModelTreeNode node) { final SModel model = node.getModel(); if (model != null) { boolean modelSeenFirstTime = true; synchronized (myTreeNodes) { Collection<SModelTreeNode> knownNodes = myTreeNodes.get(model.getReference()); if (knownNodes == null) { myTreeNodes.put(model.getReference(), knownNodes = new ArrayList<SModelTreeNode>(3)); } else { modelSeenFirstTime = false; } knownNodes.add(node); } if (modelSeenFirstTime) { ((SModelInternal) model).addModelListener(myModelChangeListener); } } refreshTreeNodes(node); } public void detach(@NotNull SModelTreeNode node) { final SModel model = node.getModel(); if (model != null) { boolean modelSeenLastTime = false; synchronized (myTreeNodes) { Collection<SModelTreeNode> knownNodes = myTreeNodes.get(model.getReference()); if (knownNodes != null) { knownNodes.remove(node); if (knownNodes.isEmpty()) { myTreeNodes.remove(model.getReference()); modelSeenLastTime = true; } } } if (modelSeenLastTime) { ((SModelInternal) model).removeModelListener(myModelChangeListener); } } } void refreshAffectedTreeNodes(SModel changed) { for (SModelTreeNode treeNode : findTreeNode(changed)) { refreshTreeNodes(treeNode); } } Iterable<SModelTreeNode> findTreeNode(SModel sm) { synchronized (myTreeNodes) { final Collection<SModelTreeNode> nodes = myTreeNodes.get(sm.getReference()); return nodes == null ? Collections.<SModelTreeNode>emptyList() : new ArrayList<SModelTreeNode>(nodes); } } void refreshTreeNodes(SModelTreeNode toRefresh) { for (TreeUpdateVisitor v : myUpdates) { toRefresh.accept(v); } } void updateNodePresentation(SModelTreeNode treeNode, boolean reloadSubTree, boolean updateAncestors) { new PresentationUpdater<SModelTreeNode>(treeNode) { @Override protected boolean isValid(SModelTreeNode treeNode) { if (!super.isValid(treeNode)) return false; final SModel model = treeNode.getModel(); if (model.isLoaded()) { return !jetbrains.mps.util.SNodeOperations.isModelDisposed(model); } return true; } }.update(reloadSubTree, updateAncestors); } private class GenStatusTracker implements ModelGenerationStatusListener { private final TreeUpdateVisitor myGenStatusVisitor; public GenStatusTracker(TreeUpdateVisitor genStatusUpdate) { myGenStatusVisitor = genStatusUpdate; } @Override public void generatedFilesChanged(SModel sm) { for (SModelTreeNode treeNode : findTreeNode(sm)) { treeNode.accept(myGenStatusVisitor); } } } private class ModelChangeListener extends SModelAdapter { @Override public void modelChangedDramatically(SModel model) { for (SModelTreeNode treeNode : findTreeNode(model)) { updateNodePresentation(treeNode, false, true); refreshTreeNodes(treeNode); } } @Override public void modelChanged(SModel model) { for (SModelTreeNode treeNode : findTreeNode(model)) { updateNodePresentation(treeNode, false, true); refreshTreeNodes(treeNode); } } @Override public void modelSaved(SModel sm) { refreshAffectedTreeNodes(sm); } @Override public void modelLoadingStateChanged(SModel sm, ModelLoadingState newState) { for (SModelTreeNode treeNode : findTreeNode(sm)) { updateNodePresentation(treeNode, false, false); } } } }