TreeUi: infinite update for some cases if fixed
[fedora-idea.git] / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUi.java
blobdb88d213b3d03c37142f1c56151b5b726d2ef5f5
1 package com.intellij.ide.util.treeView;
3 import com.intellij.ide.IdeBundle;
4 import com.intellij.openapi.application.Application;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.application.ModalityState;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.progress.ProcessCanceledException;
9 import com.intellij.openapi.progress.ProgressIndicator;
10 import com.intellij.openapi.progress.ProgressManager;
11 import com.intellij.openapi.util.*;
12 import com.intellij.openapi.util.registry.Registry;
13 import com.intellij.openapi.util.registry.RegistryValue;
14 import com.intellij.openapi.project.IndexNotReadyException;
15 import com.intellij.ui.LoadingNode;
16 import com.intellij.ui.treeStructure.Tree;
17 import com.intellij.util.Alarm;
18 import com.intellij.util.ArrayUtil;
19 import com.intellij.util.ConcurrencyUtil;
20 import com.intellij.util.Time;
21 import com.intellij.util.concurrency.WorkerThread;
22 import com.intellij.util.containers.HashSet;
23 import com.intellij.util.enumeration.EnumerationCopy;
24 import com.intellij.util.ui.tree.TreeUtil;
25 import com.intellij.util.ui.update.Activatable;
26 import com.intellij.util.ui.update.UiNotifyConnector;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
30 import javax.swing.*;
31 import javax.swing.event.TreeExpansionEvent;
32 import javax.swing.event.TreeExpansionListener;
33 import javax.swing.event.TreeSelectionEvent;
34 import javax.swing.event.TreeSelectionListener;
35 import javax.swing.tree.*;
36 import java.awt.*;
37 import java.util.*;
38 import java.util.List;
39 import java.util.concurrent.ScheduledExecutorService;
40 import java.util.concurrent.TimeUnit;
42 class AbstractTreeUi {
43 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
44 protected JTree myTree;// protected for TestNG
45 @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel;
46 private AbstractTreeStructure myTreeStructure;
47 private AbstractTreeUpdater myUpdater;
48 private Comparator<NodeDescriptor> myNodeDescriptorComparator;
49 private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() {
50 public int compare(TreeNode n1, TreeNode n2) {
51 if (isLoadingNode(n1) || isLoadingNode(n2)) return 0;
52 NodeDescriptor nodeDescriptor1 = (NodeDescriptor)((DefaultMutableTreeNode)n1).getUserObject();
53 NodeDescriptor nodeDescriptor2 = (NodeDescriptor)((DefaultMutableTreeNode)n2).getUserObject();
54 return myNodeDescriptorComparator != null
55 ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2)
56 : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex();
59 private DefaultMutableTreeNode myRootNode;
60 private final HashMap<Object, Object> myElementToNodeMap = new HashMap<Object, Object>();
61 private final HashSet<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>();
62 private TreeExpansionListener myExpansionListener;
63 private MySelectionListener mySelectionListener;
64 private WorkerThread myWorker = null;
65 private ProgressIndicator myProgress;
66 private static final int WAIT_CURSOR_DELAY = 100;
67 private AbstractTreeNode<Object> TREE_NODE_WRAPPER;
68 private boolean myRootNodeWasInitialized = false;
69 private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>();
70 private boolean myUpdateFromRootRequested;
71 private boolean myWasEverShown;
72 private boolean myUpdateIfInactive;
73 private final List<Object> myLoadingParents = new ArrayList<Object>();
74 private long myClearOnHideDelay = -1;
75 private ScheduledExecutorService ourClearanceService;
76 private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
77 private final List<Runnable> myDeferredSelections = new ArrayList<Runnable>();
78 private final List<Runnable> myDeferredExpansions = new ArrayList<Runnable>();
79 private UpdaterTreeState myUpdaterState;
80 private AbstractTreeBuilder myBuilder;
82 private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
83 private long myJanitorPollPeriod = Time.SECOND * 10;
84 private boolean myCheckStructure = false;
87 private boolean myCanYield = false;
89 private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
91 private boolean myYeildingNow;
93 private List<DefaultMutableTreeNode> myPendingNodeActions = new ArrayList<DefaultMutableTreeNode>();
94 private List<Runnable> myYeildingDoneRunnables = new ArrayList<Runnable>();
96 private Alarm myBusyAlarm = new Alarm();
97 private Runnable myWaiterForReady = new Runnable() {
98 public void run() {
99 maybeSetBusyAndScheduleWaiterForReady(false);
103 private RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
104 private RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
105 private RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimout");
107 private boolean myWasEverIndexNotReady;
109 protected final void init(AbstractTreeBuilder builder,
110 JTree tree,
111 DefaultTreeModel treeModel,
112 AbstractTreeStructure treeStructure,
113 Comparator<NodeDescriptor> comparator) {
115 init(builder, tree, treeModel, treeStructure, comparator, true);
118 protected void init(AbstractTreeBuilder builder,
119 JTree tree,
120 DefaultTreeModel treeModel,
121 AbstractTreeStructure treeStructure,
122 Comparator<NodeDescriptor> comparator,
123 boolean updateIfInactive) {
124 myBuilder = builder;
125 myTree = tree;
126 myTreeModel = treeModel;
127 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
128 myTree.setModel(myTreeModel);
129 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
130 setTreeStructure(treeStructure);
131 myNodeDescriptorComparator = comparator;
132 myUpdateIfInactive = updateIfInactive;
134 myExpansionListener = new MyExpansionListener();
135 myTree.addTreeExpansionListener(myExpansionListener);
137 mySelectionListener = new MySelectionListener();
138 myTree.addTreeSelectionListener(mySelectionListener);
140 setUpdater(getBuilder().createUpdater());
141 myProgress = getBuilder().createProgressIndicator();
142 Disposer.register(getBuilder(), getUpdater());
144 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
145 public void showNotify() {
146 if (!isReleased()) {
147 AbstractTreeUi.this.showNotify();
151 public void hideNotify() {
152 if (!isReleased()) {
153 AbstractTreeUi.this.hideNotify();
157 Disposer.register(getBuilder(), uiNotify);
160 protected void hideNotify() {
161 myBusyAlarm.cancelAllRequests();
163 if (!myWasEverShown) return;
165 if (!myNodeActions.isEmpty()) {
166 cancelBackgroundLoading();
167 myUpdateFromRootRequested = true;
170 if (myClearOnHideDelay >= 0) {
171 ourUi2Countdown.put(this, System.currentTimeMillis() + myClearOnHideDelay);
172 initClearanceServiceIfNeeded();
176 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
177 if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
179 if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
180 final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
181 final boolean isBusy = !isReady() || forcedBusy;
182 if (isBusy && tree.isShowing()) {
183 tree.setPaintBusy(true);
184 myBusyAlarm.cancelAllRequests();
185 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
186 } else {
187 tree.setPaintBusy(false);
192 private void initClearanceServiceIfNeeded() {
193 if (ourClearanceService != null) return;
195 ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor");
196 ourClearanceService.scheduleWithFixedDelay(new Runnable() {
197 public void run() {
198 cleanUpAll();
200 }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
203 private void cleanUpAll() {
204 final long now = System.currentTimeMillis();
205 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
206 for (AbstractTreeUi eachUi : uis) {
207 if (eachUi == null) continue;
208 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
209 if (timeToCleanup == null) continue;
210 if (now >= timeToCleanup.longValue()) {
211 ourUi2Countdown.remove(eachUi);
212 getBuilder().cleanUp();
217 protected void doCleanUp() {
218 final Application app = ApplicationManager.getApplication();
219 if (app != null && app.isUnitTestMode()) {
220 cleanUpNow();
222 else {
223 // we are not in EDT
224 //noinspection SSBasedInspection
225 SwingUtilities.invokeLater(new Runnable() {
226 public void run() {
227 if (!isReleased()) {
228 cleanUpNow();
235 private void disposeClearanceService() {
236 if (ourClearanceService != null) {
237 ourClearanceService.shutdown();
238 ourClearanceService = null;
242 void showNotify() {
243 ourUi2Countdown.remove(this);
245 if (!myWasEverShown || myUpdateFromRootRequested) {
246 if (wasRootNodeInitialized()) {
247 getBuilder().updateFromRoot();
249 else {
250 initRootNodeNowIfNeeded(new TreeUpdatePass(getRootNode()));
251 getBuilder().updateFromRoot();
254 myWasEverShown = true;
257 public void release() {
258 if (isReleased()) return;
260 myTree.removeTreeExpansionListener(myExpansionListener);
261 myTree.removeTreeSelectionListener(mySelectionListener);
262 disposeNode(getRootNode());
263 myElementToNodeMap.clear();
264 getUpdater().cancelAllRequests();
265 if (myWorker != null) {
266 myWorker.dispose(true);
268 TREE_NODE_WRAPPER.setValue(null);
269 if (myProgress != null) {
270 myProgress.cancel();
272 disposeClearanceService();
274 myTree = null;
275 setUpdater(null);
276 myWorker = null;
277 //todo [kirillk] afraid to do so just in release day, to uncomment
278 // myTreeStructure = null;
279 myBuilder = null;
282 public boolean isReleased() {
283 return myBuilder == null;
286 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
287 getTreeStructure().commit();
288 addNodeAction(getElementFor(node), new NodeAction() {
289 public void onReady(final DefaultMutableTreeNode node) {
290 processSmartExpand(node);
293 getUpdater().addSubtreeToUpdate(node);
294 getUpdater().performUpdate();
297 public final AbstractTreeStructure getTreeStructure() {
298 return myTreeStructure;
301 public final JTree getTree() {
302 return myTree;
305 @Nullable
306 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
307 DefaultMutableTreeNode result = null;
308 if (validateAgainstStructure) {
309 int index = 0;
310 while (true) {
311 final DefaultMutableTreeNode node = findNode(element, index);
312 if (node == null) break;
314 if (isNodeValidForElement(element, node)) {
315 result = node;
316 break;
319 index++;
322 else {
323 result = getFirstNode(element);
327 if (result != null && !isNodeInStructure(result)) {
328 disposeNode(result);
329 result = null;
332 return result;
335 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
336 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
339 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
340 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
343 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
344 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
345 final Object parentElement = getElementFor(parent);
346 if (!isInStructure(parentElement)) return false;
348 if (parent instanceof ElementNode) {
349 return ((ElementNode)parent).isValidChild(element);
351 else {
352 for (int i = 0; i < parent.getChildCount(); i++) {
353 final TreeNode child = parent.getChildAt(i);
354 final Object eachElement = getElementFor(child);
355 if (element.equals(eachElement)) return true;
359 return false;
362 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
363 boolean valid = true;
364 while (true) {
365 if (eachParent == null) {
366 valid = eachParentNode == null;
367 break;
370 if (!eachParent.equals(getElementFor(eachParentNode))) {
371 valid = false;
372 break;
375 eachParent = getTreeStructure().getParentElement(eachParent);
376 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
378 return valid;
381 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
382 DefaultMutableTreeNode node = null;
383 for (final Object pathElement : path) {
384 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
385 if (node == null) {
386 break;
389 return node;
392 public final void buildNodeForElement(Object element) {
393 getUpdater().performUpdate();
394 DefaultMutableTreeNode node = getNodeForElement(element, false);
395 if (node == null) {
396 final java.util.List<Object> elements = new ArrayList<Object>();
397 while (true) {
398 element = getTreeStructure().getParentElement(element);
399 if (element == null) {
400 break;
402 elements.add(0, element);
405 for (final Object element1 : elements) {
406 node = getNodeForElement(element1, false);
407 if (node != null) {
408 expand(node);
414 public final void buildNodeForPath(Object[] path) {
415 getUpdater().performUpdate();
416 DefaultMutableTreeNode node = null;
417 for (final Object pathElement : path) {
418 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
419 if (node != null && node != path[path.length - 1]) {
420 expand(node);
425 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
426 myNodeDescriptorComparator = nodeDescriptorComparator;
427 List<Object> pathsToExpand = new ArrayList<Object>();
428 List<Object> selectionPaths = new ArrayList<Object>();
429 TreeBuilderUtil.storePaths(getBuilder(), getRootNode(), pathsToExpand, selectionPaths, false);
430 resortChildren(getRootNode());
431 myTreeModel.nodeStructureChanged(getRootNode());
432 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
435 protected AbstractTreeBuilder getBuilder() {
436 return myBuilder;
439 private void resortChildren(DefaultMutableTreeNode node) {
440 ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
441 node.removeAllChildren();
442 Collections.sort(childNodes, myNodeComparator);
443 for (TreeNode childNode1 : childNodes) {
444 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)childNode1;
445 node.add(childNode);
446 resortChildren(childNode);
450 protected final void initRootNode() {
451 final Activatable activatable = new Activatable() {
452 public void showNotify() {
453 if (!myRootNodeWasInitialized) {
454 initRootNodeNowIfNeeded(new TreeUpdatePass(getRootNode()));
458 public void hideNotify() {
462 if (myUpdateIfInactive || ApplicationManager.getApplication().isUnitTestMode()) {
463 activatable.showNotify();
465 else {
466 new UiNotifyConnector.Once(myTree, activatable);
470 private void initRootNodeNowIfNeeded(TreeUpdatePass pass) {
471 if (myRootNodeWasInitialized) return;
473 myRootNodeWasInitialized = true;
474 Object rootElement = getTreeStructure().getRootElement();
475 addNodeAction(rootElement, new NodeAction() {
476 public void onReady(final DefaultMutableTreeNode node) {
477 processDeferredActions();
480 NodeDescriptor nodeDescriptor = getTreeStructure().createDescriptor(rootElement, null);
481 getRootNode().setUserObject(nodeDescriptor);
482 update(nodeDescriptor, false);
483 if (getElementFromDescriptor(nodeDescriptor) != null) {
484 createMapping(getElementFromDescriptor(nodeDescriptor), getRootNode());
486 addLoadingNode(getRootNode());
487 boolean willUpdate = false;
488 if (getBuilder().isAutoExpandNode(nodeDescriptor)) {
489 willUpdate = myUnbuiltNodes.contains(getRootNode());
490 expand(getRootNode());
492 if (!willUpdate) {
493 updateNodeChildren(getRootNode(), pass);
495 if (getRootNode().getChildCount() == 0) {
496 myTreeModel.nodeChanged(getRootNode());
499 if (!myLoadingParents.contains(getTreeStructure().getRootElement())) {
500 processDeferredActions();
504 private boolean update(final NodeDescriptor nodeDescriptor, boolean canBeNonEdt) {
505 if (!canBeNonEdt) {
506 assertIsDispatchThread();
509 final Application app = ApplicationManager.getApplication();
510 if (app.isDispatchThread()) {
511 return getBuilder().updateNodeDescriptor(nodeDescriptor);
512 } else {
513 app.invokeLater(new Runnable() {
514 public void run() {
515 if (!isReleased()) {
516 getBuilder().updateNodeDescriptor(nodeDescriptor);
519 }, ModalityState.stateForComponent(myTree));
520 return true;
524 private void assertIsDispatchThread() {
525 if (myTree.isShowing()) {
526 ApplicationManager.getApplication().assertIsDispatchThread();
530 private void processDeferredActions() {
531 processDeferredActions(myDeferredSelections);
532 processDeferredActions(myDeferredExpansions);
535 private void processDeferredActions(List<Runnable> actions) {
536 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
537 actions.clear();
538 for (Runnable runnable : runnables) {
539 runnable.run();
543 public void doUpdateFromRoot() {
544 updateSubtree(getRootNode());
547 public ActionCallback doUpdateFromRootCB() {
548 final ActionCallback cb = new ActionCallback();
549 getUpdater().runAfterUpdate(new Runnable() {
550 public void run() {
551 cb.setDone();
554 updateSubtree(getRootNode());
555 return cb;
558 public final void updateSubtree(DefaultMutableTreeNode node) {
559 updateSubtree(new TreeUpdatePass(node));
562 public final void updateSubtree(TreeUpdatePass pass) {
563 maybeSetBusyAndScheduleWaiterForReady(true);
565 initRootNodeNowIfNeeded(pass);
567 final DefaultMutableTreeNode node = pass.getNode();
569 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
571 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
573 getBuilder().updateNode(node);
574 updateNodeChildren(node, pass);
577 @NotNull
578 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
579 final UpdaterTreeState oldState = myUpdaterState;
580 if (oldState == null) {
581 myUpdaterState = state;
582 return state;
584 else {
585 oldState.addAll(state);
586 return oldState;
590 protected void doUpdateNode(DefaultMutableTreeNode node) {
591 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
592 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
593 Object prevElement = getElementFromDescriptor(descriptor);
594 if (prevElement == null) return;
595 boolean changes = update(descriptor, false);
596 if (getElementFromDescriptor(descriptor) == null) {
597 LOG.assertTrue(false, "element == null, updateSubtree should be invoked for parent! builder=" +
598 getBuilder() +
599 ", prevElement = " +
600 prevElement +
601 ", node = " +
602 node +
603 "; parentDescriptor=" +
604 descriptor.getParentDescriptor());
606 if (changes) {
607 updateNodeImageAndPosition(node);
611 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
612 return getBuilder().getTreeStructureElement(descriptor);
615 private void updateNodeChildren(final DefaultMutableTreeNode node, final TreeUpdatePass pass) {
616 getTreeStructure().commit();
617 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath()));
618 final boolean wasLeaf = node.getChildCount() == 0;
620 final NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
622 if (descriptor == null) return;
624 if (myUnbuiltNodes.contains(node)) {
625 processUnbuilt(node, descriptor, pass);
626 processNodeActionsIfReady(node);
627 return;
630 if (getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor))) {
631 if (queueBackgroundUpdate(node, descriptor, pass)) return;
634 final Map<Object, Integer> elementToIndexMap = collectElementToIndexMap(descriptor);
636 myUpdatingChildren.add(node);
637 processAllChildren(node, elementToIndexMap, pass).doWhenDone(new Runnable() {
638 public void run() {
639 if (canYield()) {
640 removeLoadingNode(node);
643 ArrayList<TreeNode> nodesToInsert = collectNodesToInsert(descriptor, elementToIndexMap);
645 insertNodesInto(nodesToInsert, node);
647 updateNodesToInsert(nodesToInsert, pass);
649 if (wasExpanded) {
650 expand(node);
653 if (wasExpanded || wasLeaf) {
654 expand(node, descriptor, wasLeaf);
657 myUpdatingChildren.remove(node);
659 final Object element = getElementFor(node);
660 addNodeAction(element, new NodeAction() {
661 public void onReady(final DefaultMutableTreeNode node) {
662 removeLoadingNode(node);
666 processNodeActionsIfReady(node);
671 private void expand(DefaultMutableTreeNode node) {
672 expand(new TreePath(node.getPath()));
675 private void ____expand(final TreePath path) {
676 if (path == null) return;
677 final Object last = path.getLastPathComponent();
678 final TreePath parent = path.getParentPath();
680 if (myTree.isExpanded(path)) {
681 if (last instanceof DefaultMutableTreeNode) {
682 processNodeActionsIfReady((DefaultMutableTreeNode)last);
685 else {
686 if (parent != null) {
687 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
688 final TreePath parentPath = new TreePath(parentNode.getPath());
689 if (!myTree.isExpanded(parentPath)) {
690 myUnbuiltNodes.add(parentNode);
691 expandPath(parentPath);
695 expandPath(path);
698 private void expand(final TreePath path) {
699 if (path == null) return;
700 final Object last = path.getLastPathComponent();
701 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
702 final boolean isRoot = last == myTree.getModel().getRoot();
703 final TreePath parent = path.getParentPath();
704 if (false) {
705 processNodeActionsIfReady((DefaultMutableTreeNode)last);
707 else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent))) {
708 if (last instanceof DefaultMutableTreeNode) {
709 processNodeActionsIfReady((DefaultMutableTreeNode)last);
712 else {
713 if (isLeaf && parent != null) {
714 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
715 if (parentNode != null) {
716 myUnbuiltNodes.add(parentNode);
718 expandPath(parent);
720 else {
721 expandPath(path);
726 private void processUnbuilt(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final TreeUpdatePass pass) {
727 if (getBuilder().isAlwaysShowPlus(descriptor)) return; // check for isAlwaysShowPlus is important for e.g. changing Show Members state!
729 final Object element = getBuilder().getTreeStructureElement(descriptor);
731 if (getTreeStructure().isToBuildChildrenInBackground(element)) return; //?
733 final Object[] children = getChildrenFor(element);
734 if (children.length == 0) {
735 removeLoadingNode(node);
737 else if (getBuilder().isAutoExpandNode((NodeDescriptor)node.getUserObject())) {
738 addNodeAction(getElementFor(node), new NodeAction() {
739 public void onReady(final DefaultMutableTreeNode node) {
740 final TreePath path = new TreePath(node.getPath());
741 if (getTree().isExpanded(path) || children.length == 0) {
742 removeLoadingNode(node);
743 } else {
744 maybeYeild(new ActiveRunnable() {
745 public ActionCallback run() {
746 expand(element, null);
747 return new ActionCallback.Done();
749 }, pass, node);
756 private void removeLoadingNode(final DefaultMutableTreeNode parent) {
757 for (int i = 0; i < parent.getChildCount(); i++) {
758 if (removeIfLoading(parent.getChildAt(i))) break;
760 myUnbuiltNodes.remove(parent);
763 private boolean removeIfLoading(TreeNode node) {
764 if (isLoadingNode(node)) {
765 removeNodeFromParent((MutableTreeNode)node, false);
766 return true;
769 return false;
772 //todo [kirillk] temporary consistency check
773 private Object[] getChildrenFor(final Object element) {
774 final Object[] passOne;
775 try {
776 passOne = getTreeStructure().getChildElements(element);
778 catch (IndexNotReadyException e) {
779 if (!myWasEverIndexNotReady) {
780 myWasEverIndexNotReady = true;
781 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
783 return ArrayUtil.EMPTY_OBJECT_ARRAY;
786 if (!myCheckStructure) return passOne;
788 final Object[] passTwo = getTreeStructure().getChildElements(element);
790 final HashSet two = new HashSet(Arrays.asList(passTwo));
792 if (passOne.length != passTwo.length) {
793 LOG.error(
794 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
795 element);
797 else {
798 for (Object eachInOne : passOne) {
799 if (!two.contains(eachInOne)) {
800 LOG.error(
801 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
802 element);
803 break;
808 return passOne;
811 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert, TreeUpdatePass pass) {
812 for (TreeNode aNodesToInsert : nodesToInsert) {
813 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
814 addLoadingNode(childNode);
815 updateNodeChildren(childNode, pass);
819 private ActionCallback processAllChildren(final DefaultMutableTreeNode node,
820 final Map<Object, Integer> elementToIndexMap,
821 final TreeUpdatePass pass) {
823 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
825 return maybeYeild(new ActiveRunnable() {
826 public ActionCallback run() {
827 return processAllChildren(node, elementToIndexMap, pass, childNodes);
829 }, pass, node);
832 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
833 final ActionCallback result = new ActionCallback();
835 if (isToYieldUpdateFor(node)) {
836 pass.setCurrentNode(node);
837 yieldAndRun(new Runnable() {
838 public void run() {
839 if (pass.isExpired()) return;
841 if (getUpdater().isRerunNeededFor(pass)) {
842 getUpdater().addSubtreeToUpdate(pass);
843 result.setRejected();
845 else {
846 processRunnable.run().notify(result);
849 }, pass);
851 else {
852 processRunnable.run().notify(result);
855 return result;
858 private void yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
859 myYeildingPasses.add(pass);
860 myYeildingNow = true;
861 yield(new Runnable() {
862 public void run() {
863 if (isReleased()) {
864 return;
867 runOnYeildingDone(new Runnable() {
868 public void run() {
869 if (isReleased()) {
870 return;
872 executeYeildingRequest(runnable, pass);
879 public boolean isYeildingNow() {
880 return myYeildingNow;
883 private boolean hasSheduledUpdates() {
884 return getUpdater().hasNodesToUpdate() || myLoadingParents.size() > 0;
887 private boolean hasExpandedUnbuiltNodes() {
888 for (DefaultMutableTreeNode each : myUnbuiltNodes) {
889 if (myTree.isExpanded(new TreePath(each.getPath()))) return true;
892 return false;
895 public boolean isReady() {
896 return !isYeildingNow() && !hasSheduledUpdates() && !hasExpandedUnbuiltNodes();
899 private void executeYeildingRequest(Runnable runnable, TreeUpdatePass pass) {
900 try {
901 myYeildingPasses.remove(pass);
902 runnable.run();
904 finally {
905 maybeYeildingFinished();
909 private void maybeYeildingFinished() {
910 if (myYeildingPasses.size() == 0) {
911 myYeildingNow = false;
912 flushPendingNodeActions();
916 private void flushPendingNodeActions() {
917 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
918 myPendingNodeActions.clear();
920 for (DefaultMutableTreeNode each : nodes) {
921 processNodeActionsIfReady(each);
924 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
925 for (Runnable each : actions) {
926 if (!isYeildingNow()) {
927 myYeildingDoneRunnables.remove(each);
928 each.run();
933 protected void runOnYeildingDone(Runnable onDone) {
934 getBuilder().runOnYeildingDone(onDone);
937 protected void yield(Runnable runnable) {
938 getBuilder().yield(runnable);
941 private ActionCallback processAllChildren(final DefaultMutableTreeNode node,
942 final Map<Object, Integer> elementToIndexMap,
943 final TreeUpdatePass pass,
944 final ArrayList<TreeNode> childNodes) {
947 if (pass.isExpired()) return new ActionCallback.Rejected();
949 if (childNodes.size() == 0) return new ActionCallback.Done();
952 final ActionCallback result = new ActionCallback(childNodes.size());
954 for (TreeNode childNode1 : childNodes) {
955 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)childNode1;
956 if (isLoadingNode(childNode)) {
957 result.setDone();
958 continue;
961 maybeYeild(new ActiveRunnable() {
962 @Override
963 public ActionCallback run() {
964 return processChildNode(childNode, (NodeDescriptor)childNode.getUserObject(), node, elementToIndexMap, pass);
966 }, pass, node).notify(result);
969 return result;
972 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
973 if (!canYield()) return false;
974 return getBuilder().isToYieldUpdateFor(node);
977 private Map<Object, Integer> collectElementToIndexMap(final NodeDescriptor descriptor) {
978 Map<Object, Integer> elementToIndexMap = new LinkedHashMap<Object, Integer>();
979 Object[] children = getChildrenFor(getBuilder().getTreeStructureElement(descriptor));
980 int index = 0;
981 for (Object child : children) {
982 if (!isValid(child)) continue;
983 elementToIndexMap.put(child, Integer.valueOf(index));
984 index++;
986 return elementToIndexMap;
989 private void expand(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final boolean wasLeaf) {
990 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
991 alarm.addRequest(new Runnable() {
992 public void run() {
993 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
995 }, WAIT_CURSOR_DELAY);
997 if (wasLeaf && getBuilder().isAutoExpandNode(descriptor)) {
998 expand(node);
1001 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1002 for (TreeNode node1 : nodes) {
1003 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1004 if (isLoadingNode(childNode)) continue;
1005 NodeDescriptor childDescr = (NodeDescriptor)childNode.getUserObject();
1006 if (getBuilder().isAutoExpandNode(childDescr)) {
1007 addNodeAction(getElementFor(childNode), new NodeAction() {
1008 public void onReady(DefaultMutableTreeNode node) {
1009 expand(childNode);
1012 addSubtreeToUpdate(childNode);
1016 int n = alarm.cancelAllRequests();
1017 if (n == 0) {
1018 myTree.setCursor(Cursor.getDefaultCursor());
1022 public static boolean isLoadingNode(final Object node) {
1023 return node instanceof LoadingNode;
1026 private ArrayList<TreeNode> collectNodesToInsert(final NodeDescriptor descriptor, final Map<Object, Integer> elementToIndexMap) {
1027 ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1028 for (Map.Entry<Object, Integer> entry : elementToIndexMap.entrySet()) {
1029 Object child = entry.getKey();
1030 Integer index = entry.getValue();
1031 final NodeDescriptor childDescr = getTreeStructure().createDescriptor(child, descriptor);
1032 //noinspection ConstantConditions
1033 if (childDescr == null) {
1034 LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1035 continue;
1037 childDescr.setIndex(index.intValue());
1038 update(childDescr, false);
1039 if (getElementFromDescriptor(childDescr) == null) {
1040 LOG.error("childDescr.getElement() == null, child = " + child + ", builder = " + this);
1041 continue;
1043 final DefaultMutableTreeNode childNode = createChildNode(childDescr);
1044 nodesToInsert.add(childNode);
1045 createMapping(getElementFromDescriptor(childDescr), childNode);
1047 return nodesToInsert;
1050 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1051 return new ElementNode(this, descriptor);
1054 protected boolean canYield() {
1055 return myCanYield && myYeildingUpdate.asBoolean();
1058 static class ElementNode extends DefaultMutableTreeNode {
1060 Set<Object> myElements = new HashSet<Object>();
1061 AbstractTreeUi myUi;
1064 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1065 super(descriptor);
1066 myUi = ui;
1069 @Override
1070 public void insert(final MutableTreeNode newChild, final int childIndex) {
1071 super.insert(newChild, childIndex);
1072 final Object element = myUi.getElementFor(newChild);
1073 if (element != null) {
1074 myElements.add(element);
1078 @Override
1079 public void remove(final int childIndex) {
1080 final TreeNode node = getChildAt(childIndex);
1081 super.remove(childIndex);
1082 final Object element = myUi.getElementFor(node);
1083 if (element != null) {
1084 myElements.remove(element);
1088 boolean isValidChild(Object childElement) {
1089 return myElements.contains(childElement);
1093 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1094 DefaultMutableTreeNode eachParent = kid;
1095 while (eachParent != null) {
1096 if (myUpdatingChildren.contains(eachParent)) return true;
1097 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1100 return false;
1103 private boolean queueBackgroundUpdate(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final TreeUpdatePass pass) {
1104 if (myLoadingParents.contains(getElementFromDescriptor(descriptor))) return false;
1106 myLoadingParents.add(getElementFromDescriptor(descriptor));
1108 if (!isNodeBeingBuilt(node)) {
1109 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1110 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1113 Runnable updateRunnable = new Runnable() {
1114 public void run() {
1115 if (isReleased()) return;
1117 update(descriptor, true);
1118 Object element = getElementFromDescriptor(descriptor);
1119 if (element == null) return;
1121 getChildrenFor(getBuilder().getTreeStructureElement(descriptor)); // load children
1125 Runnable postRunnable = new Runnable() {
1126 public void run() {
1127 if (isReleased()) return;
1129 updateNodeChildren(node, pass);
1131 myLoadingParents.remove(getElementFromDescriptor(descriptor));
1133 update(descriptor, false);
1134 Object element = getElementFromDescriptor(descriptor);
1136 if (element != null) {
1137 myUnbuiltNodes.remove(node);
1139 for (int i = 0; i < node.getChildCount(); i++) {
1140 TreeNode child = node.getChildAt(i);
1141 if (isLoadingNode(child)) {
1142 if (TreeBuilderUtil.isNodeSelected(myTree, node)) {
1143 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(node)), true, Condition.FALSE);
1145 removeIfLoading(child);
1146 i--;
1150 processNodeActionsIfReady(node);
1154 addTaskToWorker(updateRunnable, true, postRunnable);
1155 return true;
1158 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1159 if (isNodeBeingBuilt(node)) return;
1161 final Object o = node.getUserObject();
1162 if (!(o instanceof NodeDescriptor)) return;
1165 if (isYeildingNow()) {
1166 myPendingNodeActions.add(node);
1167 return;
1170 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1172 final List<NodeAction> actions = myNodeActions.get(element);
1173 if (actions != null) {
1174 myNodeActions.remove(element);
1175 for (NodeAction each : actions) {
1176 each.onReady(node);
1180 if (!isUpdatingParent(node)) {
1181 //if (myUpdaterState != null) {
1182 // if (myUpdaterState.process(node, myTree)) {
1183 // clearUpdaterState();
1184 // }
1187 final UpdaterTreeState state = myUpdaterState;
1188 if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1189 if (!state.restore()) {
1190 setUpdaterState(state);
1196 private void processSmartExpand(final DefaultMutableTreeNode node) {
1197 if (getBuilder().isSmartExpand() && node.getChildCount() == 1) { // "smart" expand
1198 TreeNode childNode = node.getChildAt(0);
1199 if (isLoadingNode(childNode)) return;
1200 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(childNode);
1201 expand(childPath);
1205 public boolean isLoadingChildrenFor(final Object nodeObject) {
1206 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1208 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1210 int loadingNodes = 0;
1211 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
1212 TreeNode child = node.getChildAt(i);
1213 if (isLoadingNode(child)) {
1214 loadingNodes++;
1217 return loadingNodes > 0 && loadingNodes == node.getChildCount();
1220 private boolean isParentLoading(Object nodeObject) {
1221 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1223 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1225 TreeNode eachParent = node.getParent();
1227 while (eachParent != null) {
1228 eachParent = eachParent.getParent();
1229 if (eachParent instanceof DefaultMutableTreeNode) {
1230 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
1231 if (myLoadingParents.contains(eachElement)) return true;
1235 return false;
1238 protected String getLoadingNodeText() {
1239 return IdeBundle.message("progress.searching");
1242 private ActionCallback processChildNode(final DefaultMutableTreeNode childNode,
1243 final NodeDescriptor childDescr,
1244 final DefaultMutableTreeNode node,
1245 final Map<Object, Integer> elementToIndexMap,
1246 TreeUpdatePass pass) {
1248 if (pass.isExpired()) {
1249 return new ActionCallback.Rejected();
1253 if (childDescr == null) {
1254 pass.expire();
1255 return new ActionCallback.Rejected();
1257 Object oldElement = getElementFromDescriptor(childDescr);
1258 if (oldElement == null) {
1259 pass.expire();
1260 return new ActionCallback.Rejected();
1262 boolean changes = update(childDescr, false);
1263 Object newElement = getElementFromDescriptor(childDescr);
1264 Integer index = newElement != null ? elementToIndexMap.get(getBuilder().getTreeStructureElement(childDescr)) : null;
1265 if (index != null) {
1266 if (childDescr.getIndex() != index.intValue()) {
1267 changes = true;
1269 childDescr.setIndex(index.intValue());
1271 if (index != null && changes) {
1272 updateNodeImageAndPosition(childNode);
1274 if (!oldElement.equals(newElement)) {
1275 removeMapping(oldElement, childNode);
1276 if (newElement != null) {
1277 createMapping(newElement, childNode);
1281 if (index == null) {
1282 int selectedIndex = -1;
1283 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
1284 selectedIndex = node.getIndex(childNode);
1287 if (childNode.getParent() instanceof DefaultMutableTreeNode) {
1288 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
1289 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
1290 if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
1291 insertLoadingNode(parent, false);
1296 Object disposedElement = getElementFor(childNode);
1298 removeNodeFromParent(childNode, selectedIndex >= 0);
1299 disposeNode(childNode);
1301 if (selectedIndex >= 0) {
1302 if (node.getChildCount() > 0) {
1303 if (node.getChildCount() > selectedIndex) {
1304 TreeNode newChildNode = node.getChildAt(selectedIndex);
1305 if (isValidForSelectionAdjusting(newChildNode)) {
1306 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement));
1309 else {
1310 TreeNode newChild = node.getChildAt(node.getChildCount() - 1);
1311 if (isValidForSelectionAdjusting(newChild)) {
1312 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement));
1316 else {
1317 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(node)), true, getExpiredElementCondition(disposedElement));
1321 else {
1322 elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDescr));
1323 updateNodeChildren(childNode, pass);
1326 if (node.equals(getRootNode())) {
1327 myTreeModel.nodeChanged(getRootNode());
1330 return new ActionCallback.Done();
1333 private boolean isValidForSelectionAdjusting(TreeNode node) {
1334 if (isLoadingNode(node)) return true;
1336 final Object elementInTree = getElementFor(node);
1337 if (elementInTree == null) return false;
1339 final TreeNode parentNode = node.getParent();
1340 final Object parentElementInTree = getElementFor(parentNode);
1341 if (parentElementInTree == null) return false;
1343 final Object parentElement = getTreeStructure().getParentElement(elementInTree);
1345 return parentElementInTree.equals(parentElement);
1348 private Condition getExpiredElementCondition(final Object element) {
1349 return new Condition() {
1350 public boolean value(final Object o) {
1351 return isInStructure(element);
1356 private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement) {
1357 doWithUpdaterState(new Runnable() {
1358 public void run() {
1359 TreePath toSelect = null;
1361 if (isLoadingNode(path.getLastPathComponent())) {
1362 final TreePath parentPath = path.getParentPath();
1363 if (parentPath != null) {
1364 toSelect = parentPath;
1367 else {
1368 toSelect = path;
1371 if (toSelect != null) {
1372 myTree.addSelectionPath(toSelect);
1374 if (isAdjustedSelection && myUpdaterState != null) {
1375 final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
1376 myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement);
1383 private static TreePath getPathFor(TreeNode node) {
1384 if (node instanceof DefaultMutableTreeNode) {
1385 return new TreePath(((DefaultMutableTreeNode)node).getPath());
1387 else {
1388 ArrayList nodes = new ArrayList();
1389 TreeNode eachParent = node;
1390 while (eachParent != null) {
1391 nodes.add(eachParent);
1392 eachParent = eachParent.getParent();
1395 return new TreePath(ArrayUtil.toObjectArray(nodes));
1400 private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
1401 doWithUpdaterState(new Runnable() {
1402 public void run() {
1403 if (willAdjustSelection) {
1404 final TreePath path = getPathFor(node);
1405 if (myTree.isPathSelected(path)) {
1406 myTree.removeSelectionPath(path);
1410 myTreeModel.removeNodeFromParent(node);
1415 private void expandPath(final TreePath path) {
1416 doWithUpdaterState(new Runnable() {
1417 public void run() {
1418 myTree.expandPath(path);
1423 private void doWithUpdaterState(Runnable runnable) {
1424 if (myUpdaterState != null) {
1425 myUpdaterState.process(runnable);
1427 else {
1428 runnable.run();
1432 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
1433 return descriptor.update();
1436 private void addLoadingNode(final DefaultMutableTreeNode node) {
1437 final NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1438 if (!getBuilder().isAlwaysShowPlus(descriptor)) {
1439 if (getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor))) {
1440 final boolean[] hasNoChildren = new boolean[1];
1441 Runnable runnable = new Runnable() {
1442 public void run() {
1443 if (isReleased()) return;
1445 update(descriptor, true);
1446 Object element = getBuilder().getTreeStructureElement(descriptor);
1447 if (element == null && !isValid(element)) return;
1449 Object[] children = getChildrenFor(element);
1450 hasNoChildren[0] = children.length == 0;
1454 Runnable postRunnable = new Runnable() {
1455 public void run() {
1456 if (isReleased()) return;
1458 if (hasNoChildren[0]) {
1459 update(descriptor, false);
1460 // todo please check the fix
1461 // removing loading node from the same node we've added it to, no need to look for it
1462 removeLoadingNode(node);
1463 //Object element = getElementFromDescriptor(descriptor);
1464 //if (element != null) {
1465 // DefaultMutableTreeNode node = getNodeForElement(element, false);
1466 // removeLoadingNode(node);
1472 addTaskToWorker(runnable, false, postRunnable);
1474 else {
1475 Object[] children = getChildrenFor(getBuilder().getTreeStructureElement(descriptor));
1476 if (children.length == 0) return;
1480 insertLoadingNode(node, true);
1483 private boolean isValid(Object element) {
1484 if (element instanceof ValidateableNode) {
1485 if (!((ValidateableNode)element).isValid()) return false;
1487 return getBuilder().validateNode(element);
1490 private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
1491 myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
1492 if (addToUnbuilt) {
1493 myUnbuiltNodes.add(node);
1497 protected void addTaskToWorker(final Runnable runnable, boolean first, final Runnable postRunnable) {
1498 Runnable runnable1 = new Runnable() {
1499 public void run() {
1500 if (isReleased()) return;
1502 try {
1503 Runnable runnable2 = new Runnable() {
1504 public void run() {
1505 if (isReleased()) return;
1507 final Application app = ApplicationManager.getApplication();
1508 if (app == null) return;
1510 app.runReadAction(runnable);
1511 if (postRunnable != null) {
1512 final JTree tree = myTree;
1513 if (tree != null && tree.isVisible()) {
1514 app.invokeLater(postRunnable, ModalityState.stateForComponent(tree));
1516 else {
1517 app.invokeLater(postRunnable);
1522 if (myProgress != null) {
1523 ProgressManager.getInstance().runProcess(runnable2, myProgress);
1525 else {
1526 runnable2.run();
1529 catch (ProcessCanceledException e) {
1530 //ignore
1535 if (myWorker == null || myWorker.isDisposed()) {
1536 myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
1537 myWorker.start();
1538 if (first) {
1539 myWorker.addTaskFirst(runnable1);
1541 else {
1542 myWorker.addTask(runnable1);
1544 myWorker.dispose(false);
1546 else {
1547 if (first) {
1548 myWorker.addTaskFirst(runnable1);
1550 else {
1551 myWorker.addTask(runnable1);
1556 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node) {
1557 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
1558 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1559 if (getElementFromDescriptor(descriptor) == null) return;
1560 DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
1561 if (parentNode != null) {
1562 int oldIndex = parentNode.getIndex(node);
1563 int newIndex = oldIndex;
1564 if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
1565 final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
1566 for (int i = 0; i < parentNode.getChildCount(); i++) {
1567 children.add(parentNode.getChildAt(i));
1569 Collections.sort(children, myNodeComparator);
1570 newIndex = children.indexOf(node);
1573 if (oldIndex != newIndex) {
1574 List<Object> pathsToExpand = new ArrayList<Object>();
1575 List<Object> selectionPaths = new ArrayList<Object>();
1576 TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
1577 removeNodeFromParent(node, false);
1578 myTreeModel.insertNodeInto(node, parentNode, newIndex);
1579 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
1581 else {
1582 myTreeModel.nodeChanged(node);
1585 else {
1586 myTreeModel.nodeChanged(node);
1590 public DefaultTreeModel getTreeModel() {
1591 return myTreeModel;
1594 private void insertNodesInto(ArrayList<TreeNode> nodes, DefaultMutableTreeNode parentNode) {
1595 if (nodes.isEmpty()) return;
1597 nodes = new ArrayList<TreeNode>(nodes);
1598 Collections.sort(nodes, myNodeComparator);
1600 ArrayList<TreeNode> all = TreeUtil.childrenToArray(parentNode);
1601 all.addAll(nodes);
1602 Collections.sort(all, myNodeComparator);
1604 int[] indices = new int[nodes.size()];
1605 int idx = 0;
1606 for (int i = 0; i < nodes.size(); i++) {
1607 TreeNode node = nodes.get(i);
1608 while (all.get(idx) != node) idx++;
1609 indices[i] = idx;
1610 parentNode.insert((MutableTreeNode)node, idx);
1613 myTreeModel.nodesWereInserted(parentNode, indices);
1616 private void disposeNode(DefaultMutableTreeNode node) {
1617 if (node.getChildCount() > 0) {
1618 for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
1619 disposeNode(_node);
1622 if (isLoadingNode(node)) return;
1623 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1624 if (descriptor == null) return;
1625 final Object element = getElementFromDescriptor(descriptor);
1626 removeMapping(element, node);
1627 node.setUserObject(null);
1628 node.removeAllChildren();
1631 public void addSubtreeToUpdate(final DefaultMutableTreeNode root) {
1632 addSubtreeToUpdate(root, null);
1635 public void addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
1636 getUpdater().runAfterUpdate(runAfterUpdate);
1637 getUpdater().addSubtreeToUpdate(root);
1640 public boolean wasRootNodeInitialized() {
1641 return myRootNodeWasInitialized;
1644 public void select(final Object[] elements, @Nullable final Runnable onDone) {
1645 select(elements, onDone, false);
1648 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
1649 _select(elements, onDone, addToSelection, true, false);
1652 void _select(final Object[] elements,
1653 final Runnable onDone,
1654 final boolean addToSelection,
1655 final boolean checkCurrentSelection,
1656 final boolean checkIfInStructure) {
1659 runDone(new Runnable() {
1660 public void run() {
1661 if (elements.length == 0) {
1662 runDone(onDone);
1663 return;
1666 final Set<Object> currentElements = getSelectedElements();
1668 if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
1669 boolean runSelection = false;
1670 for (Object eachToSelect : elements) {
1671 if (!currentElements.contains(eachToSelect)) {
1672 runSelection = true;
1673 break;
1677 if (!runSelection) {
1678 runDone(onDone);
1679 return;
1683 Set<Object> toSelect = new HashSet<Object>();
1684 myTree.clearSelection();
1685 toSelect.addAll(Arrays.asList(elements));
1686 if (addToSelection) {
1687 toSelect.addAll(currentElements);
1690 if (checkIfInStructure) {
1691 final Iterator<Object> allToSelect = toSelect.iterator();
1692 while (allToSelect.hasNext()) {
1693 Object each = allToSelect.next();
1694 if (!isInStructure(each)) {
1695 allToSelect.remove();
1700 final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
1702 if (wasRootNodeInitialized()) {
1703 final int[] originalRows = myTree.getSelectionRows();
1704 if (!addToSelection) {
1705 myTree.clearSelection();
1707 addNext(elementsToSelect, 0, onDone, originalRows);
1709 else {
1710 myDeferredSelections.clear();
1711 myDeferredSelections.add(new Runnable() {
1712 public void run() {
1713 select(elementsToSelect, onDone);
1721 @NotNull
1722 final Set<Object> getSelectedElements() {
1723 final TreePath[] paths = myTree.getSelectionPaths();
1725 Set<Object> result = new HashSet<Object>();
1726 if (paths != null) {
1727 for (TreePath eachPath : paths) {
1728 if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
1729 final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
1730 final Object eachElement = getElementFor(eachNode);
1731 if (eachElement != null) {
1732 result.add(eachElement);
1737 return result;
1741 private void addNext(final Object[] elements, final int i, @Nullable final Runnable onDone, final int[] originalRows) {
1742 if (i >= elements.length) {
1743 if (myTree.isSelectionEmpty()) {
1744 myTree.setSelectionRows(originalRows);
1746 runDone(onDone);
1748 else {
1749 _select(elements[i], new Runnable() {
1750 public void run() {
1751 addNext(elements, i + 1, onDone, originalRows);
1753 }, true);
1757 public void select(final Object element, @Nullable final Runnable onDone) {
1758 select(element, onDone, false);
1761 public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
1762 _select(element, onDone, addToSelection);
1765 private void _select(final Object element, final Runnable onDone, final boolean addToSelection) {
1766 final Runnable _onDone = new Runnable() {
1767 public void run() {
1768 final DefaultMutableTreeNode toSelect = getNodeForElement(element, false);
1769 if (toSelect == null) {
1770 runDone(onDone);
1771 return;
1773 final int row = myTree.getRowForPath(new TreePath(toSelect.getPath()));
1775 if (myUpdaterState != null) {
1776 myUpdaterState.addSelection(element);
1778 TreeUtil.showAndSelect(myTree, row - 2, row + 2, row, -1, addToSelection);
1779 runDone(onDone);
1782 _expand(element, _onDone, true, false);
1785 public void expand(final Object element, @Nullable final Runnable onDone) {
1786 expand(element, onDone, false);
1790 void expand(final Object element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
1791 _expand(new Object[]{element}, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure);
1794 void expand(final Object[] element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
1795 _expand(element, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure);
1798 void _expand(final Object[] element, @NotNull final Runnable onDone, final boolean parentsOnly, final boolean checkIfInStructure) {
1799 runDone(new Runnable() {
1800 public void run() {
1801 if (element.length == 0) {
1802 runDone(onDone);
1803 return;
1806 if (myUpdaterState != null) {
1807 myUpdaterState.clearExpansion();
1811 final ActionCallback done = new ActionCallback(element.length);
1812 done.doWhenDone(new Runnable() {
1813 public void run() {
1814 runDone(onDone);
1818 for (final Object toExpand : element) {
1819 _expand(toExpand, new Runnable() {
1820 public void run() {
1821 done.setDone();
1823 }, parentsOnly, checkIfInStructure);
1829 public void collapseChildren(final Object element, @Nullable final Runnable onDone) {
1830 runDone(new Runnable() {
1831 public void run() {
1832 final DefaultMutableTreeNode node = getNodeForElement(element, false);
1833 if (node != null) {
1834 getTree().collapsePath(new TreePath(node.getPath()));
1835 runDone(onDone);
1841 private void runDone(@Nullable Runnable done) {
1842 if (done == null) return;
1844 if (isYeildingNow()) {
1845 if (!myYeildingDoneRunnables.contains(done)) {
1846 myYeildingDoneRunnables.add(done);
1848 } else {
1849 done.run();
1853 private void _expand(final Object element, @NotNull final Runnable onDone, final boolean parentsOnly, boolean checkIfInStructure) {
1854 if (checkIfInStructure && !isInStructure(element)) {
1855 runDone(onDone);
1856 return;
1859 if (wasRootNodeInitialized()) {
1860 List<Object> kidsToExpand = new ArrayList<Object>();
1861 Object eachElement = element;
1862 DefaultMutableTreeNode firstVisible = null;
1863 while (true) {
1864 if (!isValid(eachElement)) break;
1866 firstVisible = getNodeForElement(eachElement, true);
1867 if (eachElement != element || !parentsOnly) {
1868 assert !kidsToExpand.contains(eachElement) :
1869 "Not a valid tree structure, walking up the structure gives many entries for element=" +
1870 eachElement +
1871 ", root=" +
1872 getTreeStructure().getRootElement();
1873 kidsToExpand.add(eachElement);
1875 if (firstVisible != null) break;
1876 eachElement = getTreeStructure().getParentElement(eachElement);
1877 if (eachElement == null) {
1878 firstVisible = null;
1879 break;
1883 if (firstVisible == null) {
1884 runDone(onDone);
1886 else if (kidsToExpand.size() == 0) {
1887 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)firstVisible.getParent();
1888 if (parentNode != null) {
1889 final TreePath parentPath = new TreePath(parentNode.getPath());
1890 if (!myTree.isExpanded(parentPath)) {
1891 expand(parentPath);
1894 runDone(onDone);
1896 else {
1897 processExpand(firstVisible, kidsToExpand, kidsToExpand.size() - 1, onDone);
1900 else {
1901 myDeferredExpansions.add(new Runnable() {
1902 public void run() {
1903 _expand(element, onDone, parentsOnly, false);
1909 private void processExpand(final DefaultMutableTreeNode toExpand,
1910 final List kidsToExpand,
1911 final int expandIndex,
1912 @NotNull final Runnable onDone) {
1913 final Object element = getElementFor(toExpand);
1914 if (element == null) return;
1916 addNodeAction(element, new NodeAction() {
1917 public void onReady(final DefaultMutableTreeNode node) {
1918 if (node.getChildCount() >= 0 && !myTree.isExpanded(new TreePath(node.getPath()))) {
1919 expand(node);
1922 if (expandIndex < 0) {
1923 runDone(onDone);
1924 return;
1927 final DefaultMutableTreeNode nextNode = getNodeForElement(kidsToExpand.get(expandIndex), false);
1928 if (nextNode != null) {
1929 processExpand(nextNode, kidsToExpand, expandIndex - 1, onDone);
1931 else {
1932 runDone(onDone);
1937 expand(toExpand);
1941 @Nullable
1942 private Object getElementFor(Object node) {
1943 if (!(node instanceof DefaultMutableTreeNode)) return null;
1944 return getElementFor((DefaultMutableTreeNode)node);
1947 @Nullable
1948 private Object getElementFor(DefaultMutableTreeNode node) {
1949 if (node != null) {
1950 final Object o = node.getUserObject();
1951 if (o instanceof NodeDescriptor) {
1952 return getElementFromDescriptor(((NodeDescriptor)o));
1956 return null;
1959 public final boolean isNodeBeingBuilt(final TreePath path) {
1960 return isNodeBeingBuilt(path.getLastPathComponent());
1963 public final boolean isNodeBeingBuilt(Object node) {
1964 if (isParentLoading(node) || isLoadingParent(node)) return true;
1966 final boolean childrenAreNoLoadedYet = isLoadingChildrenFor(node) && myUnbuiltNodes.contains(node);
1967 if (childrenAreNoLoadedYet) {
1968 if (node instanceof DefaultMutableTreeNode) {
1969 final TreePath nodePath = new TreePath(((DefaultMutableTreeNode)node).getPath());
1970 if (!myTree.isExpanded(nodePath)) return false;
1973 return true;
1977 return false;
1980 private boolean isLoadingParent(Object node) {
1981 if (!(node instanceof DefaultMutableTreeNode)) return false;
1982 return myLoadingParents.contains(getElementFor((DefaultMutableTreeNode)node));
1985 public void setTreeStructure(final AbstractTreeStructure treeStructure) {
1986 myTreeStructure = treeStructure;
1987 clearUpdaterState();
1990 public AbstractTreeUpdater getUpdater() {
1991 return myUpdater;
1994 public void setUpdater(final AbstractTreeUpdater updater) {
1995 myUpdater = updater;
1998 public DefaultMutableTreeNode getRootNode() {
1999 return myRootNode;
2002 public void setRootNode(@NotNull final DefaultMutableTreeNode rootNode) {
2003 myRootNode = rootNode;
2006 private void dropUpdaterStateIfExternalChange() {
2007 if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) {
2008 clearUpdaterState();
2012 private void clearUpdaterState() {
2013 myUpdaterState = null;
2016 private void createMapping(Object element, DefaultMutableTreeNode node) {
2017 if (!myElementToNodeMap.containsKey(element)) {
2018 myElementToNodeMap.put(element, node);
2020 else {
2021 final Object value = myElementToNodeMap.get(element);
2022 final List<DefaultMutableTreeNode> nodes;
2023 if (value instanceof DefaultMutableTreeNode) {
2024 nodes = new ArrayList<DefaultMutableTreeNode>();
2025 nodes.add((DefaultMutableTreeNode)value);
2026 myElementToNodeMap.put(element, nodes);
2028 else {
2029 nodes = (List<DefaultMutableTreeNode>)value;
2031 nodes.add(node);
2035 private void removeMapping(Object element, DefaultMutableTreeNode node) {
2036 final Object value = myElementToNodeMap.get(element);
2037 if (value == null) {
2038 return;
2040 if (value instanceof DefaultMutableTreeNode) {
2041 if (value.equals(node)) {
2042 myElementToNodeMap.remove(element);
2045 else {
2046 List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
2047 final boolean reallyRemoved = nodes.remove(node);
2048 if (reallyRemoved) {
2049 if (nodes.isEmpty()) {
2050 myElementToNodeMap.remove(element);
2056 private DefaultMutableTreeNode getFirstNode(Object element) {
2057 return findNode(element, 0);
2060 private DefaultMutableTreeNode findNode(final Object element, int startIndex) {
2061 final Object value = getBuilder().findNodeByElement(element);
2062 if (value == null) {
2063 return null;
2065 if (value instanceof DefaultMutableTreeNode) {
2066 return startIndex == 0 ? (DefaultMutableTreeNode)value : null;
2068 final List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
2069 return startIndex < nodes.size() ? nodes.get(startIndex) : null;
2072 protected Object findNodeByElement(Object element) {
2073 if (myElementToNodeMap.containsKey(element)) {
2074 return myElementToNodeMap.get(element);
2077 try {
2078 TREE_NODE_WRAPPER.setValue(element);
2079 return myElementToNodeMap.get(TREE_NODE_WRAPPER);
2081 finally {
2082 TREE_NODE_WRAPPER.setValue(null);
2086 private DefaultMutableTreeNode findNodeForChildElement(DefaultMutableTreeNode parentNode, Object element) {
2087 final Object value = myElementToNodeMap.get(element);
2088 if (value == null) {
2089 return null;
2092 if (value instanceof DefaultMutableTreeNode) {
2093 final DefaultMutableTreeNode elementNode = (DefaultMutableTreeNode)value;
2094 return parentNode.equals(elementNode.getParent()) ? elementNode : null;
2097 final List<DefaultMutableTreeNode> allNodesForElement = (List<DefaultMutableTreeNode>)value;
2098 for (final DefaultMutableTreeNode elementNode : allNodesForElement) {
2099 if (parentNode.equals(elementNode.getParent())) {
2100 return elementNode;
2104 return null;
2107 public void cancelBackgroundLoading() {
2108 if (myWorker != null) {
2109 myWorker.cancelTasks();
2111 myNodeActions.clear();
2114 private void addNodeAction(Object element, NodeAction action) {
2115 maybeSetBusyAndScheduleWaiterForReady(true);
2117 List<NodeAction> list = myNodeActions.get(element);
2118 if (list == null) {
2119 list = new ArrayList<NodeAction>();
2120 myNodeActions.put(element, list);
2122 list.add(action);
2125 private void cleanUpNow() {
2126 if (isReleased()) return;
2128 final UpdaterTreeState state = new UpdaterTreeState(this);
2130 myTree.collapsePath(new TreePath(myTree.getModel().getRoot()));
2131 myTree.clearSelection();
2132 getRootNode().removeAllChildren();
2134 myRootNodeWasInitialized = false;
2135 myNodeActions.clear();
2136 myElementToNodeMap.clear();
2137 myDeferredSelections.clear();
2138 myDeferredExpansions.clear();
2139 myLoadingParents.clear();
2140 myUnbuiltNodes.clear();
2141 myUpdateFromRootRequested = true;
2143 if (myWorker != null) {
2144 Disposer.dispose(myWorker);
2145 myWorker = null;
2148 myTree.invalidate();
2150 state.restore();
2153 public AbstractTreeUi setClearOnHideDelay(final long clearOnHideDelay) {
2154 myClearOnHideDelay = clearOnHideDelay;
2155 return this;
2158 public void setJantorPollPeriod(final long time) {
2159 myJanitorPollPeriod = time;
2162 public void setCheckStructure(final boolean checkStructure) {
2163 myCheckStructure = checkStructure;
2166 private class MySelectionListener implements TreeSelectionListener {
2167 public void valueChanged(final TreeSelectionEvent e) {
2168 dropUpdaterStateIfExternalChange();
2172 private class MyExpansionListener implements TreeExpansionListener {
2173 public void treeExpanded(TreeExpansionEvent event) {
2174 dropUpdaterStateIfExternalChange();
2176 TreePath path = event.getPath();
2177 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2178 if (!myUnbuiltNodes.contains(node)) return;
2179 myUnbuiltNodes.remove(node);
2181 getBuilder().expandNodeChildren(node);
2183 runDone(new Runnable() {
2184 public void run() {
2185 final Object element = getElementFor(node);
2187 for (int i = 0; i < node.getChildCount(); i++) {
2188 removeIfLoading(node.getChildAt(i));
2191 if (node.getChildCount() == 0) {
2192 addNodeAction(element, new NodeAction() {
2193 public void onReady(final DefaultMutableTreeNode node) {
2194 expand(element, null);
2199 processSmartExpand(node);
2204 public void treeCollapsed(TreeExpansionEvent e) {
2205 final TreePath path = e.getPath();
2206 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2207 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2209 TreePath pathToSelect = null;
2210 if (isSelectionInside(node)) {
2211 pathToSelect = new TreePath(node.getPath());
2215 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
2216 if (getBuilder().isDisposeOnCollapsing(descriptor)) {
2217 removeChildren(node);
2218 addLoadingNode(node);
2219 if (node.equals(getRootNode())) {
2220 addSelectionPath(new TreePath(getRootNode().getPath()), true, Condition.FALSE);
2222 else {
2223 myTreeModel.reload(node);
2227 if (pathToSelect != null && myTree.isSelectionEmpty()) {
2228 addSelectionPath(pathToSelect, true, Condition.FALSE);
2232 private void removeChildren(DefaultMutableTreeNode node) {
2233 EnumerationCopy copy = new EnumerationCopy(node.children());
2234 while (copy.hasMoreElements()) {
2235 disposeNode((DefaultMutableTreeNode)copy.nextElement());
2237 node.removeAllChildren();
2238 myTreeModel.nodeStructureChanged(node);
2241 private boolean isSelectionInside(DefaultMutableTreeNode parent) {
2242 TreePath path = new TreePath(myTreeModel.getPathToRoot(parent));
2243 TreePath[] paths = myTree.getSelectionPaths();
2244 if (paths == null) return false;
2245 for (TreePath path1 : paths) {
2246 if (path.isDescendant(path1)) return true;
2248 return false;
2252 public boolean isInStructure(@Nullable Object element) {
2253 Object eachParent = element;
2254 while (eachParent != null) {
2255 if (getTreeStructure().getRootElement().equals(eachParent)) return true;
2256 eachParent = getTreeStructure().getParentElement(eachParent);
2259 return false;
2262 interface NodeAction {
2263 void onReady(DefaultMutableTreeNode node);
2266 public void setCanYield(final boolean canYield) {
2267 myCanYield = canYield;
2270 public Collection<TreeUpdatePass> getYeildingPasses() {
2271 return myYeildingPasses;
2274 public boolean isBuilt(Object element) {
2275 if (!myElementToNodeMap.containsKey(element)) return false;
2276 final Object node = myElementToNodeMap.get(element);
2277 return !myUnbuiltNodes.contains(node);