/*
* 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;
}
}
}
}