/* * 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; import jetbrains.mps.ide.projectPane.logicalview.ProjectPaneTree; import jetbrains.mps.ide.projectPane.logicalview.highlighting.listeners.ModuleNodeListeners; import jetbrains.mps.ide.projectPane.logicalview.highlighting.listeners.SModelNodeListeners; import jetbrains.mps.ide.projectPane.logicalview.highlighting.visitor.ErrorChecker; import jetbrains.mps.ide.projectPane.logicalview.highlighting.visitor.GenStatusUpdater; import jetbrains.mps.ide.projectPane.logicalview.highlighting.visitor.ModifiedMarker; import jetbrains.mps.ide.projectPane.logicalview.highlighting.visitor.updates.TreeNodeUpdater; import jetbrains.mps.ide.ui.tree.MPSTree; import jetbrains.mps.ide.ui.tree.MPSTreeNode; import jetbrains.mps.ide.ui.tree.MPSTreeNodeListener; import jetbrains.mps.ide.ui.tree.TreeElement; import jetbrains.mps.ide.ui.tree.module.ProjectModuleTreeNode; import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode; import jetbrains.mps.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.module.SRepository; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ProjectPaneTreeHighlighter { private final GenStatusUpdater myGenStatusVisitor; private final ErrorChecker myErrorVisitor; private final ModifiedMarker myModifiedMarker; private final TreeNodeUpdater myUpdater; private final ThreadPoolExecutor myExecutor = new ThreadPoolExecutor(0, 3, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), new RescheduleExecutionHandler()); private final MyMPSTreeNodeListener myNodeListener = new MyMPSTreeNodeListener(); private final ProjectPaneTree myTree; // although could access one with myTree.getProject().getRepository, it seems safe to record the instance I listen to private final SRepository myProjectRepository; // containers that control listeners of module and model respectively private ModuleNodeListeners myModuleListeners; private SModelNodeListeners myModelListeners; public ProjectPaneTreeHighlighter(ProjectPaneTree tree, Project mpsProject) { myTree = tree; myProjectRepository = mpsProject.getRepository(); myUpdater = new TreeNodeUpdater(mpsProject); myGenStatusVisitor = new GenStatusUpdater(mpsProject); myErrorVisitor = new ErrorChecker(mpsProject); myModifiedMarker = new ModifiedMarker(mpsProject); } public void init() { myGenStatusVisitor.setUpdater(myUpdater).setExecutor(myExecutor); myErrorVisitor.setUpdater(myUpdater).setExecutor(myExecutor); myModifiedMarker.setUpdater(myUpdater).setExecutor(myExecutor); myTree.addTreeNodeListener(myNodeListener); } public void dispose() { myTree.removeTreeNodeListener(myNodeListener); if (myModuleListeners != null) { myModuleListeners.stopListening(); myModuleListeners = null; } if (myModelListeners != null) { myModelListeners.stopListening(myProjectRepository); myModelListeners = null; } myExecutor.shutdownNow(); myGenStatusVisitor.setUpdater(null).setExecutor(null); myErrorVisitor.setUpdater(null).setExecutor(null); myModifiedMarker.setUpdater(null).setExecutor(null); } private SModelNodeListeners getModelListeners() { if (myModelListeners == null) { myModelListeners = new SModelNodeListeners(myGenStatusVisitor, myErrorVisitor, myModifiedMarker); myModelListeners.startListening(myProjectRepository); } return myModelListeners; } private ModuleNodeListeners getModuleListeners() { if (myModuleListeners == null) { myModuleListeners = new ModuleNodeListeners(myErrorVisitor); myModuleListeners.startListening(); } return myModuleListeners; } /*package*/ void moduleNodeAdded(@NotNull ProjectModuleTreeNode node) { getModuleListeners().attach(node); } /*package*/ void moduleNodeRemoved(@NotNull ProjectModuleTreeNode node) { assert myModuleListeners != null; getModuleListeners().detach(node); } /*package*/ void modelNodeAdded(SModelTreeNode modelNode) { getModelListeners().attach(modelNode); } /*package*/ void modelNodeRemoved(SModelTreeNode modelNode) { assert myModelListeners != null; getModelListeners().detach(modelNode); } /** * Highlighter knows which visitor(s) shall run in dumb mode, while outer code controls dumb mode awareness */ public void dumbUpdate() { dispatchForHierarchy(myTree.getRootNode()); } private void dispatchForHierarchy(MPSTreeNode treeNode) { if (treeNode instanceof TreeElement) { ((TreeElement) treeNode).accept(myGenStatusVisitor); } for (MPSTreeNode node : treeNode) { dispatchForHierarchy(node); } } private class MyMPSTreeNodeListener implements MPSTreeNodeListener { @Override public void treeNodeAdded(MPSTreeNode treeNode, MPSTree tree) { if (treeNode instanceof SModelTreeNode) { SModelTreeNode modelNode = (SModelTreeNode) treeNode; if (modelNode.getModel() != null) { modelNodeAdded(modelNode); } } else if (treeNode instanceof ProjectModuleTreeNode) { moduleNodeAdded((ProjectModuleTreeNode) treeNode); } } @Override public void treeNodeRemoved(MPSTreeNode treeNode, MPSTree tree) { if (treeNode instanceof SModelTreeNode) { SModelTreeNode modelNode = (SModelTreeNode) treeNode; if (modelNode.getModel() != null) { modelNodeRemoved(modelNode); } } else if (treeNode instanceof ProjectModuleTreeNode) { moduleNodeRemoved((ProjectModuleTreeNode) treeNode); } } @Override public void treeNodeUpdated(MPSTreeNode treeNode, MPSTree tree) { } @Override public void beforeTreeDisposed(MPSTree tree) { } } /* * Policy that reschedules rejected tasks to be executed once tasks that employed available threads are over. * Re-scheduling happens from a separate thread to avoid dead-lock when executor.execute() is invoked from withing another * task being executed. Rescheduling thread dies after certain amount of inactivity not to consume resources. */ private static class RescheduleExecutionHandler implements RejectedExecutionHandler, Runnable { private final LinkedBlockingQueue<Runnable> myQueue = new LinkedBlockingQueue<Runnable>(); private volatile Thread myRescheduleThread; private ThreadPoolExecutor myExecutor; @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { if (executor.isShutdown()) { return; } myQueue.add(r); if (myRescheduleThread == null) { synchronized (this) { if (myRescheduleThread == null) { myExecutor = executor; myRescheduleThread = new Thread(this); myRescheduleThread.start(); } } } } @Override public void run() { do { try { Runnable r = myQueue.poll(3000, TimeUnit.MILLISECONDS); if (r == null) { // die, if there's no new element for 3 seconds break; } myExecutor.getQueue().put(r); } catch (InterruptedException ex) { // ignore, not too much of a trouble to loose tree status update } } while (true); // if queue is empty for quite a long time, stop the thread. synchronized (this) { myExecutor = null; myRescheduleThread = null; } } } }