49b8366e96837359f1b7f1871b2ddb14a09bca3d
[idea/community.git] / platform / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUi.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ide.util.treeView;
17
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.openapi.application.Application;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.progress.*;
23 import com.intellij.openapi.project.IndexNotReadyException;
24 import com.intellij.openapi.util.*;
25 import com.intellij.openapi.util.registry.Registry;
26 import com.intellij.openapi.util.registry.RegistryValue;
27 import com.intellij.ui.LoadingNode;
28 import com.intellij.ui.treeStructure.Tree;
29 import com.intellij.util.Alarm;
30 import com.intellij.util.ArrayUtil;
31 import com.intellij.util.Time;
32 import com.intellij.util.concurrency.WorkerThread;
33 import com.intellij.util.containers.ContainerUtil;
34 import com.intellij.util.containers.HashSet;
35 import com.intellij.util.enumeration.EnumerationCopy;
36 import com.intellij.util.ui.UIUtil;
37 import com.intellij.util.ui.tree.TreeUtil;
38 import com.intellij.util.ui.update.Activatable;
39 import com.intellij.util.ui.update.UiNotifyConnector;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42
43 import javax.swing.*;
44 import javax.swing.event.*;
45 import javax.swing.tree.*;
46 import java.awt.*;
47 import java.awt.event.FocusAdapter;
48 import java.awt.event.FocusEvent;
49 import java.util.*;
50 import java.util.List;
51 import java.util.concurrent.TimeUnit;
52 import java.util.concurrent.atomic.AtomicBoolean;
53 import java.util.concurrent.locks.Lock;
54 import java.util.concurrent.locks.ReentrantLock;
55
56 public class AbstractTreeUi {
57   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
58   protected JTree myTree;// protected for TestNG
59   @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel;
60   private AbstractTreeStructure myTreeStructure;
61   private AbstractTreeUpdater myUpdater;
62
63   private Comparator<NodeDescriptor> myNodeDescriptorComparator;
64   private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() {
65     public int compare(TreeNode n1, TreeNode n2) {
66       if (isLoadingNode(n1) || isLoadingNode(n2)) return 0;
67       NodeDescriptor nodeDescriptor1 = getDescriptorFrom(((DefaultMutableTreeNode)n1));
68       NodeDescriptor nodeDescriptor2 = getDescriptorFrom(((DefaultMutableTreeNode)n2));
69       return myNodeDescriptorComparator != null
70              ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2)
71              : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex();
72     }
73   };
74   long myOwnComparatorStamp;
75   long myLastComparatorStamp;
76
77   private DefaultMutableTreeNode myRootNode;
78   private final HashMap<Object, Object> myElementToNodeMap = new HashMap<Object, Object>();
79   private final HashSet<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>();
80   private TreeExpansionListener myExpansionListener;
81   private MySelectionListener mySelectionListener;
82
83   private WorkerThread myWorker = null;
84   private final Set<Runnable> myActiveWorkerTasks = new HashSet<Runnable>();
85
86   private ProgressIndicator myProgress;
87   private static final int WAIT_CURSOR_DELAY = 100;
88   private AbstractTreeNode<Object> TREE_NODE_WRAPPER;
89
90   private boolean myRootNodeWasQueuedToInitialize = false;
91   private boolean myRootNodeInitialized = false;
92
93   private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>();
94   private boolean myUpdateFromRootRequested;
95   private boolean myWasEverShown;
96   private boolean myUpdateIfInactive;
97
98   private final Map<Object, UpdateInfo> myLoadedInBackground = new HashMap<Object, UpdateInfo>();
99   private final Map<Object, List<NodeAction>> myNodeChildrenActions = new HashMap<Object, List<NodeAction>>();
100
101   private long myClearOnHideDelay = -1;
102   private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
103
104   private final Set<Runnable> myDeferredSelections = new HashSet<Runnable>();
105   private final Set<Runnable> myDeferredExpansions = new HashSet<Runnable>();
106
107   private boolean myCanProcessDeferredSelections;
108
109   private UpdaterTreeState myUpdaterState;
110   private AbstractTreeBuilder myBuilder;
111
112   private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
113   private long myJanitorPollPeriod = Time.SECOND * 10;
114
115   private boolean myCanYield = false;
116
117   private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
118
119   private boolean myYeildingNow;
120
121   private final Set<DefaultMutableTreeNode> myPendingNodeActions = new HashSet<DefaultMutableTreeNode>();
122   private final Set<Runnable> myYeildingDoneRunnables = new HashSet<Runnable>();
123
124   private final Alarm myBusyAlarm = new Alarm();
125   private final Runnable myWaiterForReady = new Runnable() {
126     public void run() {
127       maybeSetBusyAndScheduleWaiterForReady(false);
128     }
129   };
130
131   private final RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
132   private final RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
133   private final RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimeout");
134
135   private boolean myWasEverIndexNotReady;
136   private boolean myShowing;
137   private final FocusAdapter myFocusListener = new FocusAdapter() {
138     @Override
139     public void focusGained(FocusEvent e) {
140       maybeReady();
141     }
142   };
143   private final Set<DefaultMutableTreeNode> myNotForSmartExpand = new HashSet<DefaultMutableTreeNode>();
144   private TreePath myRequestedExpand;
145
146   private TreePath mySilentExpand;
147   private TreePath mySilentSelect;
148
149   private final ActionCallback myInitialized = new ActionCallback();
150   private BusyObject.Impl myBusyObject = new BusyObject.Impl() {
151     @Override
152     protected boolean isReady() {
153       return AbstractTreeUi.this.isReady(true);
154     }
155   };
156
157   private boolean myPassthroughMode = false;
158
159
160   private final Set<Object> myAutoExpandRoots = new HashSet<Object>();
161   private final RegistryValue myAutoExpandDepth = Registry.get("ide.tree.autoExpandMaxDepth");
162
163   private final Set<DefaultMutableTreeNode> myWillBeExpaned = new HashSet<DefaultMutableTreeNode>();
164   private SimpleTimerTask myCleanupTask;
165
166   private AtomicBoolean myCancelRequest = new AtomicBoolean();
167   private Lock myStateLock = new ReentrantLock();
168
169   private AtomicBoolean myResettingToReadyNow = new AtomicBoolean();
170
171   private Map<Progressive, ProgressIndicator> myBatchIndicators = new HashMap<Progressive, ProgressIndicator>();
172   private Map<Progressive, ActionCallback> myBatchCallbacks = new HashMap<Progressive, ActionCallback>();
173
174   private Map<DefaultMutableTreeNode, DefaultMutableTreeNode> myCancelledBuild = new WeakHashMap<DefaultMutableTreeNode, DefaultMutableTreeNode>();
175
176   private boolean mySelectionIsAdjusted;
177   private boolean myReleaseRequested;
178
179   private Set<Object> myRevalidatedObjects = new HashSet<Object>();
180
181   private Set<Runnable> myUserRunnables = new HashSet<Runnable>();
182
183   private Alarm myMaybeReady = new Alarm();
184   private Runnable myMaybeReadyRunnable = new Runnable() {
185     @Override
186     public void run() {
187       maybeReady();
188     }
189   };
190
191   protected void init(AbstractTreeBuilder builder,
192                       JTree tree,
193                       DefaultTreeModel treeModel,
194                       AbstractTreeStructure treeStructure,
195                       @Nullable Comparator<NodeDescriptor> comparator,
196                       boolean updateIfInactive) {
197     myBuilder = builder;
198     myTree = tree;
199     myTreeModel = treeModel;
200     addModelListenerToDianoseAccessOutsideEdt();
201     TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
202     myTree.setModel(myTreeModel);
203     setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
204     setTreeStructure(treeStructure);
205     myNodeDescriptorComparator = comparator;
206     myUpdateIfInactive = updateIfInactive;
207
208     UIUtil.invokeLaterIfNeeded(new Runnable() {
209       public void run() {
210         if (!wasRootNodeInitialized()) {
211           if (myRootNode.getChildCount() == 0) {
212             insertLoadingNode(myRootNode, true);
213           }
214         }
215       }
216     });
217
218     myExpansionListener = new MyExpansionListener();
219     myTree.addTreeExpansionListener(myExpansionListener);
220
221     mySelectionListener = new MySelectionListener();
222     myTree.addTreeSelectionListener(mySelectionListener);
223
224     setUpdater(getBuilder().createUpdater());
225     myProgress = getBuilder().createProgressIndicator();
226     Disposer.register(getBuilder(), getUpdater());
227
228     final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
229       public void showNotify() {
230         myShowing = true;
231         myWasEverShown = true;
232         if (canInitiateNewActivity()) {
233           activate(true);
234         }
235       }
236
237       public void hideNotify() {
238         myShowing = false;
239         if (canInitiateNewActivity()) {
240           deactivate();
241         }
242       }
243     });
244     Disposer.register(getBuilder(), uiNotify);
245
246     myTree.addFocusListener(myFocusListener);
247   }
248
249
250   boolean isNodeActionsPending() {
251     return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty();
252   }
253
254   private void clearNodeActions() {
255     myNodeActions.clear();
256     myNodeChildrenActions.clear();
257   }
258
259   private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
260     if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
261
262     if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
263       final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
264       final boolean isBusy = !isReady(true) || forcedBusy;
265       if (isBusy && tree.isShowing()) {
266         tree.setPaintBusy(true);
267         myBusyAlarm.cancelAllRequests();
268         myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
269       }
270       else {
271         tree.setPaintBusy(false);
272       }
273     }
274   }
275
276   private void setHoldSize(boolean holdSize) {
277     if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
278       final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
279       tree.setHoldSize(holdSize);
280     }
281   }
282
283   private void cleanUpAll() {
284     final long now = System.currentTimeMillis();
285     final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
286     for (AbstractTreeUi eachUi : uis) {
287       if (eachUi == null) continue;
288       final Long timeToCleanup = ourUi2Countdown.get(eachUi);
289       if (timeToCleanup == null) continue;
290       if (now >= timeToCleanup.longValue()) {
291         ourUi2Countdown.remove(eachUi);
292         Runnable runnable = new Runnable() {
293           public void run() {
294             if (!canInitiateNewActivity()) return;
295
296             myCleanupTask = null;
297             getBuilder().cleanUp();
298           }
299         };
300         if (isPassthroughMode()) {
301           runnable.run();
302         }
303         else {
304           UIUtil.invokeLaterIfNeeded(runnable);
305         }
306       }
307     }
308   }
309
310   protected void doCleanUp() {
311     Runnable cleanup = new Runnable() {
312       public void run() {
313         if (canInitiateNewActivity()) {
314           cleanUpNow();
315         }
316       }
317     };
318
319     if (isPassthroughMode()) {
320       cleanup.run();
321     }
322     else {
323       UIUtil.invokeLaterIfNeeded(cleanup);
324     }
325   }
326
327   private ActionCallback invokeLaterIfNeeded(@NotNull final Runnable runnable) {
328     final ActionCallback result = new ActionCallback();
329
330     Runnable actual = new Runnable() {
331       public void run() {
332         runnable.run();
333         result.setDone();
334       }
335     };
336
337     if (isPassthroughMode() || (!isEdt() && (!isTreeShowing() && !myWasEverShown))) {
338       actual.run();
339     }
340     else {
341       UIUtil.invokeLaterIfNeeded(actual);
342     }
343
344     return result;
345   }
346
347   public void activate(boolean byShowing) {
348     cancelCurrentCleanupTask();
349
350     myCanProcessDeferredSelections = true;
351     ourUi2Countdown.remove(this);
352
353     if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) {
354       getBuilder().updateFromRoot();
355     }
356
357     getUpdater().showNotify();
358
359     myWasEverShown |= byShowing;
360   }
361
362   private void cancelCurrentCleanupTask() {
363     if (myCleanupTask != null) {
364       myCleanupTask.cancel();
365       myCleanupTask = null;
366     }
367   }
368
369   public void deactivate() {
370     getUpdater().hideNotify();
371     myBusyAlarm.cancelAllRequests();
372
373     if (!myWasEverShown) return;
374
375     if (!isReady()) {
376       cancelUpdate();
377       myUpdateFromRootRequested = true;
378     }
379
380     if (getClearOnHideDelay() >= 0) {
381       ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
382       sheduleCleanUpAll();
383     }
384   }
385
386   private void sheduleCleanUpAll() {
387     cancelCurrentCleanupTask();
388
389     myCleanupTask = SimpleTimer.getInstance().setUp(new Runnable() {
390       public void run() {
391         cleanUpAll();
392       }
393     }, getClearOnHideDelay());
394   }
395
396   public void requestRelease() {
397     myReleaseRequested = true;
398     cancelUpdate().doWhenDone(new Runnable() {
399       public void run() {
400         releaseNow();
401       }
402     });
403   }
404
405   private void releaseNow() {
406     try {
407       acquireLock();
408
409       myTree.removeTreeExpansionListener(myExpansionListener);
410       myTree.removeTreeSelectionListener(mySelectionListener);
411       myTree.removeFocusListener(myFocusListener);
412
413       disposeNode(getRootNode());
414       myElementToNodeMap.clear();
415       getUpdater().cancelAllRequests();
416       if (myWorker != null) {
417         myWorker.dispose(true);
418         clearWorkerTasks();
419       }
420       TREE_NODE_WRAPPER.setValue(null);
421       if (myProgress != null) {
422         myProgress.cancel();
423       }
424
425       cancelCurrentCleanupTask();
426
427       myTree = null;
428       setUpdater(null);
429       myWorker = null;
430       myTreeStructure = null;
431       myBuilder.releaseUi();
432       myBuilder = null;
433
434       clearNodeActions();
435
436       myDeferredSelections.clear();
437       myDeferredExpansions.clear();
438       myYeildingDoneRunnables.clear();
439     }
440     catch (InterruptedException e) {
441       LOG.info(e);
442     } finally {
443       releaseLock();
444     }
445   }
446
447   public boolean isReleased() {
448     return myBuilder == null;
449   }
450
451   protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
452     if (!myUnbuiltNodes.contains(node)) return;
453     if (isLoadedInBackground(getElementFor(node))) return;
454
455     getTreeStructure().commit();
456     addSubtreeToUpdate(node);
457     getUpdater().performUpdate();
458   }
459
460   public final AbstractTreeStructure getTreeStructure() {
461     return myTreeStructure;
462   }
463
464   public final JTree getTree() {
465     return myTree;
466   }
467
468   @Nullable
469   private NodeDescriptor getDescriptorFrom(DefaultMutableTreeNode node) {
470     return (NodeDescriptor)node.getUserObject();
471   }
472
473   @Nullable
474   public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
475     DefaultMutableTreeNode result = null;
476     if (validateAgainstStructure) {
477       int index = 0;
478       while (true) {
479         final DefaultMutableTreeNode node = findNode(element, index);
480         if (node == null) break;
481
482         if (isNodeValidForElement(element, node)) {
483           result = node;
484           break;
485         }
486
487         index++;
488       }
489     }
490     else {
491       result = getFirstNode(element);
492     }
493
494
495     if (result != null && !isNodeInStructure(result)) {
496       disposeNode(result);
497       result = null;
498     }
499
500     return result;
501   }
502
503   private boolean isNodeInStructure(DefaultMutableTreeNode node) {
504     return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
505   }
506
507   private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
508     return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
509   }
510
511   private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
512     final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
513     final Object parentElement = getElementFor(parent);
514     if (!isInStructure(parentElement)) return false;
515
516     if (parent instanceof ElementNode) {
517       return ((ElementNode)parent).isValidChild(element);
518     }
519     else {
520       for (int i = 0; i < parent.getChildCount(); i++) {
521         final TreeNode child = parent.getChildAt(i);
522         final Object eachElement = getElementFor(child);
523         if (element.equals(eachElement)) return true;
524       }
525     }
526
527     return false;
528   }
529
530   private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
531     boolean valid = true;
532     while (true) {
533       if (eachParent == null) {
534         valid = eachParentNode == null;
535         break;
536       }
537
538       if (!eachParent.equals(getElementFor(eachParentNode))) {
539         valid = false;
540         break;
541       }
542
543       eachParent = getTreeStructure().getParentElement(eachParent);
544       eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
545     }
546     return valid;
547   }
548
549   public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
550     DefaultMutableTreeNode node = null;
551     for (final Object pathElement : path) {
552       node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
553       if (node == null) {
554         break;
555       }
556     }
557     return node;
558   }
559
560   public final void buildNodeForElement(Object element) {
561     getUpdater().performUpdate();
562     DefaultMutableTreeNode node = getNodeForElement(element, false);
563     if (node == null) {
564       final java.util.List<Object> elements = new ArrayList<Object>();
565       while (true) {
566         element = getTreeStructure().getParentElement(element);
567         if (element == null) {
568           break;
569         }
570         elements.add(0, element);
571       }
572
573       for (final Object element1 : elements) {
574         node = getNodeForElement(element1, false);
575         if (node != null) {
576           expand(node, true);
577         }
578       }
579     }
580   }
581
582   public final void buildNodeForPath(Object[] path) {
583     getUpdater().performUpdate();
584     DefaultMutableTreeNode node = null;
585     for (final Object pathElement : path) {
586       node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
587       if (node != null && node != path[path.length - 1]) {
588         expand(node, true);
589       }
590     }
591   }
592
593   public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
594     myNodeDescriptorComparator = nodeDescriptorComparator;
595     myLastComparatorStamp = -1;
596     getBuilder().queueUpdateFrom(getTreeStructure().getRootElement(), true);
597   }
598
599   protected AbstractTreeBuilder getBuilder() {
600     return myBuilder;
601   }
602
603   protected final void initRootNode() {
604     if (myUpdateIfInactive) {
605       activate(false);
606     }
607     else {
608       myUpdateFromRootRequested = true;
609     }
610   }
611
612   private boolean initRootNodeNowIfNeeded(final TreeUpdatePass pass) {
613     boolean wasCleanedUp = false;
614     if (myRootNodeWasQueuedToInitialize) {
615       Object root = getTreeStructure().getRootElement();
616       assert root != null : "Root element cannot be null";
617
618       Object currentRoot = getElementFor(myRootNode);
619
620       if (Comparing.equal(root, currentRoot)) return false;
621
622       Object rootAgain = getTreeStructure().getRootElement();
623       if (root != rootAgain && !root.equals(rootAgain)) {
624         assert false : "getRootElement() if called twice must return either root1 == root2 or root1.equals(root2)";
625       }
626
627       cleanUpNow();
628       wasCleanedUp = true;
629     }
630
631     if (myRootNodeWasQueuedToInitialize) return wasCleanedUp;
632
633     myRootNodeWasQueuedToInitialize = true;
634
635     final Object rootElement = getTreeStructure().getRootElement();
636     addNodeAction(rootElement, new NodeAction() {
637       public void onReady(final DefaultMutableTreeNode node) {
638         processDeferredActions();
639       }
640     }, false);
641
642
643     final Ref<NodeDescriptor> rootDescriptor = new Ref<NodeDescriptor>(null);
644     final boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(rootElement);
645
646     Runnable build = new Runnable() {
647       public void run() {
648         rootDescriptor.set(getTreeStructure().createDescriptor(rootElement, null));
649         getRootNode().setUserObject(rootDescriptor.get());
650         update(rootDescriptor.get(), true);
651       }
652     };
653
654
655     Runnable update = new Runnable() {
656       public void run() {
657         if (getElementFromDescriptor(rootDescriptor.get()) != null) {
658           createMapping(getElementFromDescriptor(rootDescriptor.get()), getRootNode());
659         }
660
661
662         insertLoadingNode(getRootNode(), true);
663
664         boolean willUpdate = false;
665         if (isAutoExpand(rootDescriptor.get())) {
666           willUpdate = myUnbuiltNodes.contains(getRootNode());
667           expand(getRootNode(), true);
668         }
669         if (!willUpdate) {
670           updateNodeChildren(getRootNode(), pass, null, false, false, false, true);
671         }
672         if (getRootNode().getChildCount() == 0) {
673           myTreeModel.nodeChanged(getRootNode());
674         }
675       }
676     };
677
678     if (bgLoading) {
679       queueToBackground(build, update, rootDescriptor).doWhenProcessed(new Runnable() {
680         @Override
681         public void run() {
682           myRootNodeInitialized = true;
683           processNodeActionsIfReady(myRootNode);
684         }
685       });
686     }
687     else {
688       build.run();
689       update.run();
690       myRootNodeInitialized = true;
691       processNodeActionsIfReady(myRootNode);
692     }
693
694     return wasCleanedUp;
695   }
696
697   private boolean isAutoExpand(NodeDescriptor descriptor) {
698     return isAutoExpand(descriptor, true);
699   }
700
701   private boolean isAutoExpand(NodeDescriptor descriptor, boolean validate) {
702     if (descriptor == null) return false;
703
704     boolean autoExpand = getBuilder().isAutoExpandNode(descriptor);
705
706     Object element = getElementFromDescriptor(descriptor);
707     if (validate) {
708       autoExpand = validateAutoExpand(autoExpand, element);
709     }
710
711     if (!autoExpand && !myTree.isRootVisible()) {
712       if (element != null && element.equals(getTreeStructure().getRootElement())) return true;
713     }
714
715     return autoExpand;
716   }
717
718   private boolean validateAutoExpand(boolean autoExpand, Object element) {
719     if (autoExpand) {
720       int distance = getDistanceToAutoExpandRoot(element);
721       if (distance < 0) {
722         myAutoExpandRoots.add(element);
723       }
724       else {
725         if (distance >= myAutoExpandDepth.asInteger() - 1) {
726           autoExpand = false;
727         }
728       }
729
730       if (autoExpand) {
731         DefaultMutableTreeNode node = getNodeForElement(element, false);
732         if (isInVisibleAutoExpandChain(node)) {
733           autoExpand = true;
734         }
735         else {
736           autoExpand = false;
737         }
738       }
739     }
740     return autoExpand;
741   }
742
743   private boolean isInVisibleAutoExpandChain(DefaultMutableTreeNode child) {
744     TreeNode eachParent = child;
745     while (eachParent != null) {
746
747       if (myRootNode == eachParent) return true;
748
749       NodeDescriptor eachDescriptor = getDescriptorFrom((DefaultMutableTreeNode)eachParent);
750       if (!isAutoExpand(eachDescriptor, false)) {
751         TreePath path = getPathFor(eachParent);
752         if (myWillBeExpaned.contains(path.getLastPathComponent()) || (myTree.isExpanded(path) && myTree.isVisible(path))) {
753           return true;
754         }
755         else {
756           return false;
757         }
758       }
759       eachParent = eachParent.getParent();
760     }
761
762     return false;
763   }
764
765   private int getDistanceToAutoExpandRoot(Object element) {
766     int distance = 0;
767
768     Object eachParent = element;
769     while (eachParent != null) {
770       if (myAutoExpandRoots.contains(eachParent)) break;
771       eachParent = getTreeStructure().getParentElement(eachParent);
772       distance++;
773     }
774
775     return eachParent != null ? distance : -1;
776   }
777
778   private boolean isAutoExpand(DefaultMutableTreeNode node) {
779     return isAutoExpand(getDescriptorFrom(node));
780   }
781
782   private AsyncResult<Boolean> update(final NodeDescriptor nodeDescriptor, boolean now) {
783     final AsyncResult<Boolean> result = new AsyncResult<Boolean>();
784
785     if (now || isPassthroughMode()) {
786       return new AsyncResult<Boolean>().setDone(_update(nodeDescriptor));
787     }
788
789     Object element = getElementFromDescriptor(nodeDescriptor);
790     boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(element);
791
792     boolean edt = isEdt();
793     if (bgLoading) {
794       if (edt) {
795         final Ref<Boolean> changes = new Ref<Boolean>(false);
796         queueToBackground(new Runnable() {
797           public void run() {
798             changes.set(_update(nodeDescriptor));
799           }
800         }, new Runnable() {
801           public void run() {
802             result.setDone(changes.get());
803           }
804         }, nodeDescriptor);
805       }
806       else {
807         result.setDone(_update(nodeDescriptor));
808       }
809     }
810     else {
811       if (edt || !myWasEverShown) {
812         result.setDone(_update(nodeDescriptor));
813       }
814       else {
815         UIUtil.invokeLaterIfNeeded(new Runnable() {
816           public void run() {
817             execute(new Runnable() {
818               public void run() {
819                 result.setDone(_update(nodeDescriptor));
820               }
821             });
822           }
823         });
824       }
825     }
826
827     result.doWhenDone(new AsyncResult.Handler<Boolean>() {
828       public void run(Boolean changes) {
829         if (changes) {
830           final long updateStamp = nodeDescriptor.getUpdateCount();
831           UIUtil.invokeLaterIfNeeded(new Runnable() {
832             public void run() {
833               Object element = nodeDescriptor.getElement();
834               DefaultMutableTreeNode node = getNodeForElement(element, false);
835               if (node != null) {
836                 TreePath path = getPathFor(node);
837                 if (path != null && myTree.isVisible(path)) {
838                   updateNodeImageAndPosition(node, false);
839                 }
840               }
841             }
842           });
843         }
844       }
845     });
846
847
848     return result;
849   }
850
851   private boolean _update(final NodeDescriptor nodeDescriptor) {
852     try {
853       final Ref<Boolean> update = new Ref<Boolean>();
854       try {
855         acquireLock();
856         execute(new Runnable() {
857           public void run() {
858             nodeDescriptor.setUpdateCount(nodeDescriptor.getUpdateCount() + 1);
859             update.set(getBuilder().updateNodeDescriptor(nodeDescriptor));
860           }
861         });
862       }
863       catch (InterruptedException e) {
864         throw new ProcessCanceledException();
865       } catch (ProcessCanceledException e) {
866         throw e;        
867       } finally {
868         releaseLock();
869       }
870       return update.get();
871     }
872     catch (IndexNotReadyException e) {
873       warnOnIndexNotReady();
874       return false;
875     }
876   }
877
878   private void assertIsDispatchThread() {
879     if (isPassthroughMode()) return;
880
881     if ((isTreeShowing() || myWasEverShown) && !isEdt()) {
882       LOG.error("Must be in event-dispatch thread");
883     }
884   }
885
886   private boolean isEdt() {
887     return SwingUtilities.isEventDispatchThread();
888   }
889
890   private boolean isTreeShowing() {
891     return myShowing;
892   }
893
894   private void assertNotDispatchThread() {
895     if (isPassthroughMode()) return;
896
897     if (isEdt()) {
898       LOG.error("Must not be in event-dispatch thread");
899     }
900   }
901
902   private void processDeferredActions() {
903     processDeferredActions(myDeferredSelections);
904     processDeferredActions(myDeferredExpansions);
905   }
906
907   private void processDeferredActions(Set<Runnable> actions) {
908     final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
909     actions.clear();
910     for (Runnable runnable : runnables) {
911       runnable.run();
912     }
913   }
914
915   //todo: to make real callback
916   public ActionCallback queueUpdate(Object element) {
917     try {
918       AbstractTreeUpdater updater = getUpdater();
919       if (updater == null) {
920         return new ActionCallback.Rejected();
921       }
922
923       final ActionCallback result = new ActionCallback();
924       DefaultMutableTreeNode node = getNodeForElement(element, false);
925       if (node != null) {
926         addSubtreeToUpdate(node);
927       }
928       else {
929         addSubtreeToUpdate(getRootNode());
930       }
931
932       updater.runAfterUpdate(new Runnable() {
933         public void run() {
934           result.setDone();
935         }
936       });
937       return result;
938     }
939     catch (ProcessCanceledException e) {
940       return new ActionCallback.Rejected();
941     }
942   }
943
944   public void doUpdateFromRoot() {
945     updateSubtree(getRootNode(), false);
946   }
947
948   public ActionCallback doUpdateFromRootCB() {
949     final ActionCallback cb = new ActionCallback();
950     getUpdater().runAfterUpdate(new Runnable() {
951       public void run() {
952         cb.setDone();
953       }
954     });
955     updateSubtree(getRootNode(), false);
956     return cb;
957   }
958
959   public final void updateSubtree(DefaultMutableTreeNode node, boolean canSmartExpand) {
960     updateSubtree(new TreeUpdatePass(node), canSmartExpand);
961   }
962
963   public final void updateSubtree(TreeUpdatePass pass, boolean canSmartExpand) {
964     if (getUpdater() != null) {
965       getUpdater().addSubtreeToUpdate(pass);
966     }
967     else {
968       updateSubtreeNow(pass, canSmartExpand);
969     }
970   }
971
972   final void updateSubtreeNow(TreeUpdatePass pass, boolean canSmartExpand) {
973
974
975     maybeSetBusyAndScheduleWaiterForReady(true);
976     setHoldSize(true);
977
978     boolean consumed = initRootNodeNowIfNeeded(pass);
979     if (consumed) return;
980
981     final DefaultMutableTreeNode node = pass.getNode();
982
983     if (!(node.getUserObject() instanceof NodeDescriptor)) return;
984
985     setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
986
987     boolean forceUpdate = true;
988     TreePath path = getPathFor(node);
989     boolean invisible = !myTree.isExpanded(path) && (path.getParentPath() == null || !myTree.isExpanded(path.getParentPath()));
990
991     if (invisible && myUnbuiltNodes.contains(node)) {
992       forceUpdate = false;
993     }
994
995     updateNodeChildren(node, pass, null, false, canSmartExpand, forceUpdate, false);
996   }
997
998   private boolean isToBuildInBackground(NodeDescriptor descriptor) {
999     return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor));
1000   }
1001
1002   @NotNull
1003   UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
1004     if (myUpdaterState != null && myUpdaterState.equals(state)) return state;
1005
1006     final UpdaterTreeState oldState = myUpdaterState;
1007     if (oldState == null) {
1008       myUpdaterState = state;
1009       return state;
1010     }
1011     else {
1012       oldState.addAll(state);
1013       return oldState;
1014     }
1015   }
1016
1017   protected void doUpdateNode(final DefaultMutableTreeNode node) {
1018     if (!(node.getUserObject() instanceof NodeDescriptor)) return;
1019     final NodeDescriptor descriptor = getDescriptorFrom(node);
1020     final Object prevElement = getElementFromDescriptor(descriptor);
1021     if (prevElement == null) return;
1022     update(descriptor, false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1023       public void run(Boolean changes) {
1024         if (!isValid(descriptor)) {
1025           if (isInStructure(prevElement)) {
1026             getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement));
1027             return;
1028           }
1029         }
1030         if (changes) {
1031           updateNodeImageAndPosition(node, true);
1032         }
1033       }
1034     });
1035   }
1036
1037   public Object getElementFromDescriptor(NodeDescriptor descriptor) {
1038     return getBuilder().getTreeStructureElement(descriptor);
1039   }
1040
1041   private void updateNodeChildren(final DefaultMutableTreeNode node,
1042                                   final TreeUpdatePass pass,
1043                                   @Nullable final LoadedChildren loadedChildren,
1044                                   final boolean forcedNow,
1045                                   final boolean toSmartExpand,
1046                                   final boolean forceUpdate,
1047                                   final boolean descriptorIsUpToDate) {
1048
1049     removeFromCancelled(node);
1050
1051     execute(new Runnable() {
1052       public void run() {
1053         try {
1054           getTreeStructure().commit();
1055
1056
1057           final NodeDescriptor descriptor = getDescriptorFrom(node);
1058           if (descriptor == null) {
1059             removeFromUnbuilt(node);
1060             removeLoading(node, true);
1061             return;
1062           }
1063
1064           final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node);
1065           final boolean wasLeaf = node.getChildCount() == 0;
1066
1067
1068           boolean bgBuild = isToBuildInBackground(descriptor);
1069           boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded;
1070
1071           if (notRequiredToUpdateChildren && forceUpdate && !wasExpanded) {
1072             boolean alwaysPlus = getBuilder().isAlwaysShowPlus(descriptor);
1073             if (alwaysPlus && wasLeaf) {
1074               notRequiredToUpdateChildren = false;
1075             }
1076             else {
1077               notRequiredToUpdateChildren = alwaysPlus;
1078             }
1079           }
1080
1081           final Ref<LoadedChildren> preloaded = new Ref<LoadedChildren>(loadedChildren);
1082           boolean descriptorWasUpdated = descriptorIsUpToDate;
1083
1084           if (notRequiredToUpdateChildren) {
1085             if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) {
1086               insertLoadingNode(node, true);
1087             }
1088             return;
1089           }
1090
1091           if (!forcedNow) {
1092             if (!bgBuild) {
1093               if (myUnbuiltNodes.contains(node)) {
1094                 if (!descriptorWasUpdated) {
1095                   update(descriptor, true);
1096                   descriptorWasUpdated = true;
1097                 }
1098
1099                 if (processAlwaysLeaf(node)) return;
1100
1101                 Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null);
1102
1103                 if (unbuilt.getFirst()) return;
1104                 preloaded.set(unbuilt.getSecond());
1105               }
1106             }
1107           }
1108
1109
1110           final boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded);
1111
1112           if (!forcedNow && isToBuildInBackground(descriptor)) {
1113             if (processAlwaysLeaf(node)) return;
1114
1115             queueBackgroundUpdate(
1116               new UpdateInfo(descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorWasUpdated), node);
1117             return;
1118           }
1119           else {
1120             if (!descriptorWasUpdated) {
1121               update(descriptor, false).doWhenDone(new Runnable() {
1122                 public void run() {
1123                   if (processAlwaysLeaf(node)) return;
1124                   updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
1125                 }
1126               });
1127             }
1128             else {
1129               if (processAlwaysLeaf(node)) return;
1130
1131               updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
1132             }
1133           }
1134         }
1135         finally {
1136           if (isReleased()) return;
1137
1138           processNodeActionsIfReady(node);
1139         }
1140       }
1141     }, node);
1142   }
1143
1144   private boolean processAlwaysLeaf(DefaultMutableTreeNode node) {
1145     Object element = getElementFor(node);
1146     NodeDescriptor desc = getDescriptorFrom(node);
1147
1148     if (desc == null) return false;
1149
1150     if (getTreeStructure().isAlwaysLeaf(element)) {
1151       removeFromUnbuilt(node);
1152       removeLoading(node, true);
1153
1154       if (node.getChildCount() > 0) {
1155         final TreeNode[] children = new TreeNode[node.getChildCount()];
1156         for (int i = 0; i < node.getChildCount(); i++) {
1157           children[i] = node.getChildAt(i);
1158         }
1159
1160         if (isSelectionInside(node)) {
1161           addSelectionPath(getPathFor(node), true, Condition.TRUE, null);
1162         }
1163
1164         processInnerChange(new Runnable() {
1165           public void run() {
1166             for (TreeNode each : children) {
1167               removeNodeFromParent((MutableTreeNode)each, true);
1168               disposeNode((DefaultMutableTreeNode)each);
1169             }
1170           }
1171         });
1172       }
1173
1174       removeFromUnbuilt(node);
1175       desc.setWasDeclaredAlwaysLeaf(true);
1176       processNodeActionsIfReady(node);
1177       return true;
1178     }
1179     else {
1180       boolean wasLeaf = desc.isWasDeclaredAlwaysLeaf();
1181       desc.setWasDeclaredAlwaysLeaf(false);
1182
1183       if (wasLeaf) {
1184         insertLoadingNode(node, true);
1185       }
1186
1187       return false;
1188     }
1189   }
1190
1191   private boolean isChildNodeForceUpdate(DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) {
1192     TreePath path = getPathFor(node);
1193     return parentForceUpdate && (parentExpanded || myTree.isExpanded(path));
1194   }
1195
1196   private void updateNodeChildrenNow(final DefaultMutableTreeNode node,
1197                                      final TreeUpdatePass pass,
1198                                      final LoadedChildren preloadedChildren,
1199                                      final boolean toSmartExpand,
1200                                      final boolean wasExpanded,
1201                                      final boolean wasLeaf,
1202                                      final boolean forceUpdate) {
1203     if (!canInitiateNewActivity()) {
1204       throw new ProcessCanceledException();
1205     }
1206
1207     final NodeDescriptor descriptor = getDescriptorFrom(node);
1208
1209     final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren);
1210     final LoadedChildren loadedChildren =
1211       preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray());
1212
1213
1214     addToUpdating(node);
1215     pass.setCurrentNode(node);
1216
1217     final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
1218
1219     removeFromUnbuilt(node);
1220
1221     processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren)
1222       .doWhenDone(new Runnable() {
1223         public void run() {
1224           if (isDisposed(node)) {
1225             removeFromUpdating(node);
1226             return;
1227           }
1228
1229           removeLoading(node, false);
1230
1231           final boolean expanded = isExpanded(node, wasExpanded);
1232
1233           if (expanded) {
1234             myWillBeExpaned.add(node);
1235           }
1236           else {
1237             myWillBeExpaned.remove(node);
1238           }
1239
1240           collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren)
1241             .doWhenDone(new AsyncResult.Handler<ArrayList<TreeNode>>() {
1242               public void run(ArrayList<TreeNode> nodesToInsert) {
1243                 insertNodesInto(nodesToInsert, node);
1244                 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
1245                 removeLoading(node, false);
1246                 removeFromUpdating(node);
1247
1248                 if (node.getChildCount() > 0) {
1249                   if (expanded) {
1250                     expand(node, canSmartExpand);
1251                   }
1252                 }
1253
1254                 final Object element = getElementFor(node);
1255                 addNodeAction(element, new NodeAction() {
1256                   public void onReady(final DefaultMutableTreeNode node) {
1257                     removeLoading(node, false);
1258                   }
1259                 }, false);
1260
1261                 processNodeActionsIfReady(node);
1262               }
1263             }).doWhenProcessed(new Runnable() {
1264             public void run() {
1265               myWillBeExpaned.remove(node);
1266               removeFromUpdating(node);
1267               processNodeActionsIfReady(node);
1268             }
1269           });
1270         }
1271       }).doWhenRejected(new Runnable() {
1272       public void run() {
1273         removeFromUpdating(node);
1274         processNodeActionsIfReady(node);
1275       }
1276     });
1277   }
1278
1279   private boolean isDisposed(DefaultMutableTreeNode node) {
1280     return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
1281   }
1282
1283   private void expandSilently(TreePath path) {
1284     assertIsDispatchThread();
1285
1286     try {
1287       mySilentExpand = path;
1288       getTree().expandPath(path);
1289     }
1290     finally {
1291       mySilentExpand = null;
1292     }
1293   }
1294
1295   private void addSelectionSilently(TreePath path) {
1296     assertIsDispatchThread();
1297
1298     try {
1299       mySilentSelect = path;
1300       getTree().getSelectionModel().addSelectionPath(path);
1301     }
1302     finally {
1303       mySilentSelect = null;
1304     }
1305   }
1306
1307   private void expand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1308     expand(new TreePath(node.getPath()), canSmartExpand);
1309   }
1310
1311   private void expand(final TreePath path, boolean canSmartExpand) {
1312     if (path == null) return;
1313
1314
1315     final Object last = path.getLastPathComponent();
1316     boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
1317     final boolean isRoot = last == myTree.getModel().getRoot();
1318     final TreePath parent = path.getParentPath();
1319     if (isRoot && !myTree.isExpanded(path)) {
1320       if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) {
1321         insertLoadingNode((DefaultMutableTreeNode)last, false);
1322       }
1323       expandPath(path, canSmartExpand);
1324     }
1325     else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last) && !isCancelled(last))) {
1326       if (last instanceof DefaultMutableTreeNode) {
1327         processNodeActionsIfReady((DefaultMutableTreeNode)last);
1328       }
1329     }
1330     else {
1331       if (isLeaf && (myUnbuiltNodes.contains(last) || isCancelled(last))) {
1332         insertLoadingNode((DefaultMutableTreeNode)last, true);
1333         expandPath(path, canSmartExpand);
1334       }
1335       else if (isLeaf && parent != null) {
1336         final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
1337         if (parentNode != null) {
1338           addToUnbuilt(parentNode);
1339         }
1340         expandPath(parent, canSmartExpand);
1341       }
1342       else {
1343         expandPath(path, canSmartExpand);
1344       }
1345     }
1346   }
1347
1348   private void addToUnbuilt(DefaultMutableTreeNode node) {
1349     myUnbuiltNodes.add(node);
1350   }
1351
1352   private void removeFromUnbuilt(DefaultMutableTreeNode node) {
1353     myUnbuiltNodes.remove(node);
1354   }
1355
1356   private Pair<Boolean, LoadedChildren> processUnbuilt(final DefaultMutableTreeNode node,
1357                                                        final NodeDescriptor descriptor,
1358                                                        final TreeUpdatePass pass,
1359                                                        final boolean isExpanded,
1360                                                        final LoadedChildren loadedChildren) {
1361     final Ref<Pair<Boolean, LoadedChildren>> result = new Ref<Pair<Boolean, LoadedChildren>>();
1362
1363     execute(new Runnable() {
1364       public void run() {
1365         if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) {
1366           result.set(new Pair<Boolean, LoadedChildren>(true, null));
1367           return;
1368         }
1369
1370         final Object element = getElementFor(node);
1371
1372         addToUpdating(node);
1373
1374         try {
1375           final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element));
1376
1377           boolean processed;
1378
1379           if (children.getElements().size() == 0) {
1380             removeFromUnbuilt(node);
1381             removeLoading(node, true);
1382             processed = true;
1383           }
1384           else {
1385             if (isAutoExpand(node)) {
1386               addNodeAction(getElementFor(node), new NodeAction() {
1387                 public void onReady(final DefaultMutableTreeNode node) {
1388                   final TreePath path = new TreePath(node.getPath());
1389                   if (getTree().isExpanded(path) || children.getElements().size() == 0) {
1390                     removeLoading(node, false);
1391                   }
1392                   else {
1393                     maybeYeild(new ActiveRunnable() {
1394                       public ActionCallback run() {
1395                         expand(element, null);
1396                         return new ActionCallback.Done();
1397                       }
1398                     }, pass, node);
1399                   }
1400                 }
1401               }, false);
1402             }
1403             processed = false;
1404           }
1405
1406           removeFromUpdating(node);
1407
1408           processNodeActionsIfReady(node);
1409
1410           result.set(new Pair<Boolean, LoadedChildren>(processed, children));
1411         }
1412         finally {
1413           removeFromUpdating(node);
1414         }
1415       }
1416     });
1417
1418     return result.get();
1419   }
1420
1421   private boolean removeIfLoading(TreeNode node) {
1422     if (isLoadingNode(node)) {
1423       moveSelectionToParentIfNeeded(node);
1424       removeNodeFromParent((MutableTreeNode)node, false);
1425       return true;
1426     }
1427
1428     return false;
1429   }
1430
1431   private void moveSelectionToParentIfNeeded(TreeNode node) {
1432     TreePath path = getPathFor(node);
1433     if (myTree.getSelectionModel().isPathSelected(path)) {
1434       TreePath parentPath = path.getParentPath();
1435       myTree.getSelectionModel().removeSelectionPath(path);
1436       if (parentPath != null) {
1437         myTree.getSelectionModel().addSelectionPath(parentPath);
1438       }
1439     }
1440   }
1441
1442   //todo [kirillk] temporary consistency check
1443   private Object[] getChildrenFor(final Object element) {
1444     final Ref<Object[]> passOne = new Ref<Object[]>();
1445     try {
1446       acquireLock();
1447       execute(new Runnable() {
1448         public void run() {
1449           passOne.set(getTreeStructure().getChildElements(element));
1450         }
1451       });
1452     }
1453     catch (IndexNotReadyException e) {
1454       warnOnIndexNotReady();
1455       return ArrayUtil.EMPTY_OBJECT_ARRAY;
1456     }
1457     catch (InterruptedException e) {
1458       throw new ProcessCanceledException();
1459     } finally {
1460       releaseLock();
1461     }
1462
1463     if (!Registry.is("ide.tree.checkStructure")) return passOne.get();
1464
1465     final Object[] passTwo = getTreeStructure().getChildElements(element);
1466
1467     final HashSet two = new HashSet(Arrays.asList(passTwo));
1468
1469     if (passOne.get().length != passTwo.length) {
1470       LOG.error(
1471         "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1472         element);
1473     }
1474     else {
1475       for (Object eachInOne : passOne.get()) {
1476         if (!two.contains(eachInOne)) {
1477           LOG.error(
1478             "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1479             element);
1480           break;
1481         }
1482       }
1483     }
1484
1485     return passOne.get();
1486   }
1487
1488   private void warnOnIndexNotReady() {
1489     if (!myWasEverIndexNotReady) {
1490       myWasEverIndexNotReady = true;
1491       LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
1492     }
1493   }
1494
1495   private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert,
1496                                    TreeUpdatePass pass,
1497                                    boolean canSmartExpand,
1498                                    boolean forceUpdate) {
1499     for (TreeNode aNodesToInsert : nodesToInsert) {
1500       DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
1501       updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
1502     }
1503   }
1504
1505   private ActionCallback processExistingNodes(final DefaultMutableTreeNode node,
1506                                               final MutualMap<Object, Integer> elementToIndexMap,
1507                                               final TreeUpdatePass pass,
1508                                               final boolean canSmartExpand,
1509                                               final boolean forceUpdate,
1510                                               final boolean wasExpaned,
1511                                               final LoadedChildren preloaded) {
1512
1513     final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
1514     return maybeYeild(new ActiveRunnable() {
1515       public ActionCallback run() {
1516         if (pass.isExpired()) return new ActionCallback.Rejected();
1517         if (childNodes.size() == 0) return new ActionCallback.Done();
1518
1519
1520         final ActionCallback result = new ActionCallback(childNodes.size());
1521
1522         for (TreeNode each : childNodes) {
1523           final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each;
1524           if (isLoadingNode(eachChild)) {
1525             result.setDone();
1526             continue;
1527           }
1528
1529           final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned);
1530
1531           maybeYeild(new ActiveRunnable() {
1532             @Override
1533             public ActionCallback run() {
1534               return processExistingNode(eachChild, getDescriptorFrom(eachChild), node, elementToIndexMap, pass, canSmartExpand,
1535                                          childForceUpdate, preloaded);
1536             }
1537           }, pass, node).notify(result);
1538
1539           if (result.isRejected()) {
1540             break;
1541           }
1542         }
1543
1544         return result;
1545       }
1546     }, pass, node);
1547   }
1548
1549   private boolean isRerunNeeded(TreeUpdatePass pass) {
1550     if (pass.isExpired() || !canInitiateNewActivity()) return false;
1551
1552     final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
1553
1554     return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
1555   }
1556
1557   private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
1558     final ActionCallback result = new ActionCallback();
1559
1560     if (isRerunNeeded(pass)) {
1561       getUpdater().addSubtreeToUpdate(pass);
1562       result.setRejected();
1563     }
1564     else {
1565       if (isToYieldUpdateFor(node)) {
1566         pass.setCurrentNode(node);
1567         boolean wasRun = yieldAndRun(new Runnable() {
1568           public void run() {
1569             if (pass.isExpired()) {
1570               result.setRejected();
1571               return;
1572             }
1573
1574             if (isRerunNeeded(pass)) {
1575               runDone(new Runnable() {
1576                 public void run() {
1577                   if (!pass.isExpired()) {
1578                     getUpdater().addSubtreeToUpdate(pass);
1579                   }
1580                 }
1581               });
1582               result.setRejected();
1583             }
1584             else {
1585               try {
1586                 execute(processRunnable).notify(result);
1587               }
1588               catch (ProcessCanceledException e) {
1589                 pass.expire();
1590                 result.setRejected();
1591                 cancelUpdate();
1592               }
1593             }
1594           }
1595         }, pass);
1596         if (!wasRun) {
1597           result.setRejected();
1598         }
1599       }
1600       else {
1601         try {
1602           execute(processRunnable).notify(result);
1603         }
1604         catch (ProcessCanceledException e) {
1605           pass.expire();
1606           result.setRejected();
1607           cancelUpdate();
1608         }
1609       }
1610     }
1611
1612     return result;
1613   }
1614
1615   private ActionCallback execute(final ActiveRunnable runnable) throws ProcessCanceledException {
1616     final ActionCallback result = new ActionCallback();
1617     execute(new Runnable() {
1618       public void run() {
1619         runnable.run().notify(result);
1620       }
1621     });
1622     return result;
1623   }
1624
1625   private void execute(Runnable runnable)  {
1626     execute(runnable, null);
1627   }
1628
1629   private void execute(Runnable runnable, @Nullable DefaultMutableTreeNode node) throws ProcessCanceledException {
1630     try {
1631       if (!canInitiateNewActivity()) {
1632         throw new ProcessCanceledException();
1633       }
1634
1635       runnable.run();
1636
1637       if (!canInitiateNewActivity()) {
1638         throw new ProcessCanceledException();
1639       }
1640     }
1641     catch (ProcessCanceledException e) {
1642       if (node != null) {
1643         addToCancelled(node);
1644       }
1645       if (!isReleased()) {
1646         setCancelRequested(true);
1647         resetToReady();
1648       } 
1649       throw e;
1650     }
1651   }
1652
1653   private boolean canInitiateNewActivity() {
1654     return !isCancelProcessed() && !myReleaseRequested && !isReleased();
1655   }
1656
1657   private ActionCallback resetToReady() {
1658     final ActionCallback result = new ActionCallback();
1659
1660     if (isReady()) {
1661       result.setDone();
1662       return result;
1663     }
1664
1665     myResettingToReadyNow.set(true);
1666
1667     invokeLaterIfNeeded(new Runnable() {
1668       public void run() {
1669         Progressive[] progressives = myBatchIndicators.keySet().toArray(new Progressive[myBatchIndicators.size()]);
1670         for (Progressive each : progressives) {
1671           myBatchIndicators.remove(each).cancel();
1672           myBatchCallbacks.remove(each).setRejected();
1673         }
1674
1675         resetToReadyNow().notify(result);
1676       }
1677     });
1678
1679     return result;
1680   }
1681
1682   private ActionCallback resetToReadyNow() {
1683     if (isReleased()) return new ActionCallback.Rejected();
1684
1685     assertIsDispatchThread();
1686
1687     DefaultMutableTreeNode[] uc = myUpdatingChildren.toArray(new DefaultMutableTreeNode[myUpdatingChildren.size()]);
1688     for (DefaultMutableTreeNode each : uc) {
1689       resetIncompleteNode(each);
1690     }
1691
1692
1693     Object[] bg = myLoadedInBackground.keySet().toArray(new Object[myLoadedInBackground.size()]);
1694     for (Object each : bg) {
1695       resetIncompleteNode(getNodeForElement(each, false));
1696     }
1697
1698     myUpdaterState = null;
1699     getUpdater().reset();
1700
1701
1702     myYeildingNow = false;
1703     myYeildingPasses.clear();
1704     myYeildingDoneRunnables.clear();
1705
1706     myNodeActions.clear();
1707     myNodeChildrenActions.clear();
1708
1709     myUpdatingChildren.clear();
1710     myLoadedInBackground.clear();
1711
1712     myDeferredExpansions.clear();
1713     myDeferredSelections.clear();
1714
1715     ActionCallback result = getReady(this);
1716     result.doWhenDone(new Runnable() {
1717       public void run() {
1718         myResettingToReadyNow.set(false);
1719         setCancelRequested(false);
1720       }
1721     });
1722
1723     maybeReady();
1724
1725     return result;
1726   }
1727
1728   public void addToCancelled(DefaultMutableTreeNode node) {
1729     myCancelledBuild.put(node, node);
1730   }
1731
1732   public void removeFromCancelled(DefaultMutableTreeNode node) {
1733     myCancelledBuild.remove(node);
1734   }
1735
1736   public boolean isCancelled(Object node) {
1737     if (node instanceof DefaultMutableTreeNode) {
1738       return myCancelledBuild.containsKey((DefaultMutableTreeNode)node);
1739     } else {
1740       return false;
1741     }
1742   }
1743
1744   private void resetIncompleteNode(DefaultMutableTreeNode node) {
1745     if (myReleaseRequested) return;
1746
1747     addToCancelled(node);
1748
1749     if (!isExpanded(node, false)) {
1750       node.removeAllChildren();
1751       if (!getTreeStructure().isAlwaysLeaf(getElementFor(node))) {
1752         insertLoadingNode(node, true);
1753       }
1754     }
1755     else {
1756       removeFromUnbuilt(node);
1757       removeLoading(node, true);
1758     }
1759   }
1760
1761   private boolean yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
1762     myYeildingPasses.add(pass);
1763     myYeildingNow = true;
1764     yield(new Runnable() {
1765       public void run() {
1766         if (isReleased()) return;
1767
1768         runOnYieldingDone(new Runnable() {
1769           public void run() {
1770             if (isReleased()) return;
1771
1772             executeYieldingRequest(runnable, pass);
1773           }
1774         });
1775       }
1776     });
1777
1778     return true;
1779   }
1780
1781   public boolean isYeildingNow() {
1782     return myYeildingNow;
1783   }
1784
1785   private boolean hasSheduledUpdates() {
1786     return getUpdater().hasNodesToUpdate();
1787   }
1788
1789   public boolean isReady() {
1790     return isReady(false);
1791   }
1792
1793   public boolean isReady(boolean attempt) {
1794     Boolean ready = _isReady(attempt);
1795     return ready != null && ready.booleanValue();
1796   }
1797
1798   @Nullable
1799   public Boolean _isReady(boolean attempt) {
1800     Boolean ready = checkValue(new Computable<Boolean>() {
1801       @Override
1802       public Boolean compute() {
1803         return Boolean.valueOf(isIdle() && !hasPendingWork() && !isNodeActionsPending());
1804       }
1805     }, attempt, null);
1806
1807     return ready != null && ready.booleanValue();
1808   }
1809
1810   private Boolean checkValue(Computable<Boolean> computable, boolean attempt, Boolean defaultValue) {
1811     boolean toRelease = true;
1812     try {
1813       if (attempt) {
1814         if (!attemptLock()) {
1815           toRelease = false;
1816           return defaultValue != null ? defaultValue : computable.compute();
1817         }
1818       } else {
1819         acquireLock();
1820       }
1821       return computable.compute();
1822     }
1823     catch (InterruptedException e) {
1824       LOG.info(e);
1825       return defaultValue;
1826     } finally {
1827       if (toRelease) {
1828         releaseLock();
1829       }
1830     }
1831   }
1832
1833   public String getStatus() {
1834     return "isReady=" + isReady() + "\n" +
1835            " isIdle=" + isIdle() + "\n" +
1836            "  isYeildingNow=" + isYeildingNow() + "\n" +
1837            "  isWorkerBusy=" + isWorkerBusy() + "\n" +
1838            "  hasUpdatingNow=" + hasUpdatingNow() + "\n" +
1839            "  isLoadingInBackgroundNow=" + isLoadingInBackgroundNow() + "\n" +
1840            " hasPendingWork=" + hasPendingWork() + "\n" +
1841            "  hasNodesToUpdate=" + hasNodesToUpdate() + "\n" +
1842            "  updaterState=" + myUpdaterState + "\n" +
1843            "  hasScheduledUpdates=" + hasSheduledUpdates() + "\n" +
1844            "  isPostponedMode=" + getUpdater().isInPostponeMode() + "\n" +
1845            " nodeActions=" + myNodeActions.keySet() + "\n" +
1846            " nodeChildrenActions=" + myNodeChildrenActions.keySet() + "\n" +
1847            "isReleased=" + isReleased() + "\n" +
1848            " isReleaseRequested=" + isReleaseRequested() + "\n" +
1849            "isCancelProcessed=" + isCancelProcessed() + "\n" +
1850            " isCancelRequested=" + myCancelRequest + "\n" +
1851            " isResettingToReadyNow=" + myResettingToReadyNow + "\n" +
1852            "canInitiateNewActivity=" + canInitiateNewActivity();
1853   }
1854
1855   public boolean hasPendingWork() {
1856     return hasNodesToUpdate() || (myUpdaterState != null && myUpdaterState.isProcessingNow()) || (hasSheduledUpdates() && !getUpdater().isInPostponeMode());
1857   }
1858
1859   public boolean isIdle() {
1860     return !isYeildingNow() && !isWorkerBusy() && !hasUpdatingNow() && !isLoadingInBackgroundNow();
1861   }
1862
1863   private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
1864     try {
1865       try {
1866         myYeildingPasses.remove(pass);
1867         runnable.run();
1868       }
1869       finally {
1870         if (!isReleased()) {
1871           maybeYeildingFinished();
1872         }
1873       }
1874     }
1875     catch (ProcessCanceledException e) {
1876       resetToReady();
1877     }
1878   }
1879
1880   private void maybeYeildingFinished() {
1881     if (myYeildingPasses.size() == 0) {
1882       myYeildingNow = false;
1883       flushPendingNodeActions();
1884     }
1885   }
1886
1887   void maybeReady() {
1888     assertIsDispatchThread();
1889
1890     if (isReleased()) return;
1891
1892     Boolean ready = _isReady(true);
1893     if (ready != null && ready.booleanValue()) {
1894       myRevalidatedObjects.clear();
1895
1896       setCancelRequested(false);
1897       myResettingToReadyNow.set(false);
1898
1899       myInitialized.setDone();
1900
1901       if (canInitiateNewActivity()) {
1902         if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) {
1903           UpdaterTreeState oldState = myUpdaterState;
1904           if (!myUpdaterState.restore(null)) {
1905             setUpdaterState(oldState);
1906           }
1907
1908           if (!isReady()) {
1909             return;
1910           }
1911         }
1912       }
1913
1914       setHoldSize(false);
1915
1916       if (myTree.isShowing()) {
1917         if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
1918           TreeUtil.ensureSelection(myTree);
1919         }
1920       }
1921
1922       if (myInitialized.isDone()) {
1923         myBusyObject.onReady();
1924       }
1925
1926       if (canInitiateNewActivity()) {
1927         TreePath[] selection = getTree().getSelectionPaths();
1928         Rectangle visible = getTree().getVisibleRect();
1929         if (selection != null) {
1930           for (TreePath each : selection) {
1931             Rectangle bounds = getTree().getPathBounds(each);
1932             if (bounds != null && (visible.contains(bounds) || visible.intersects(bounds))) {
1933               getTree().repaint(bounds);
1934             }
1935           }
1936         }
1937       }
1938     } else if (ready == null) {
1939       scheduleMaybeReady();
1940     }
1941   }
1942
1943   private void scheduleMaybeReady() {
1944     myMaybeReady.cancelAllRequests();
1945     myMaybeReady.addRequest(myMaybeReadyRunnable, Registry.intValue("ide.tree.waitForReadySchedule"));
1946   }
1947
1948   private void flushPendingNodeActions() {
1949     final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
1950     myPendingNodeActions.clear();
1951
1952     for (DefaultMutableTreeNode each : nodes) {
1953       processNodeActionsIfReady(each);
1954     }
1955
1956     final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
1957     for (Runnable each : actions) {
1958       if (!isYeildingNow()) {
1959         myYeildingDoneRunnables.remove(each);
1960         each.run();
1961       }
1962     }
1963
1964     maybeReady();
1965   }
1966
1967   protected void runOnYieldingDone(Runnable onDone) {
1968     getBuilder().runOnYeildingDone(onDone);
1969   }
1970
1971   protected void yield(Runnable runnable) {
1972     getBuilder().yield(runnable);
1973   }
1974
1975   private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
1976     if (!canYield()) return false;
1977     return getBuilder().isToYieldUpdateFor(node);
1978   }
1979
1980   private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor,
1981                                                                @Nullable LoadedChildren preloadedChildren) {
1982     MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
1983     List children = preloadedChildren != null
1984                     ? preloadedChildren.getElements()
1985                     : Arrays.asList(getChildrenFor(getBuilder().getTreeStructureElement(descriptor)));
1986     int index = 0;
1987     for (Object child : children) {
1988       if (!isValid(child)) continue;
1989       elementToIndexMap.put(child, Integer.valueOf(index));
1990       index++;
1991     }
1992     return elementToIndexMap;
1993   }
1994
1995   private void expand(final DefaultMutableTreeNode node,
1996                       final NodeDescriptor descriptor,
1997                       final boolean wasLeaf,
1998                       final boolean canSmartExpand) {
1999     final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
2000     alarm.addRequest(new Runnable() {
2001       public void run() {
2002         myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
2003       }
2004     }, WAIT_CURSOR_DELAY);
2005
2006     if (wasLeaf && isAutoExpand(descriptor)) {
2007       expand(node, canSmartExpand);
2008     }
2009
2010     ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
2011     for (TreeNode node1 : nodes) {
2012       final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
2013       if (isLoadingNode(childNode)) continue;
2014       NodeDescriptor childDescr = getDescriptorFrom(childNode);
2015       if (isAutoExpand(childDescr)) {
2016         addNodeAction(getElementFor(childNode), new NodeAction() {
2017           public void onReady(DefaultMutableTreeNode node) {
2018             expand(childNode, canSmartExpand);
2019           }
2020         }, false);
2021         addSubtreeToUpdate(childNode);
2022       }
2023     }
2024
2025     int n = alarm.cancelAllRequests();
2026     if (n == 0) {
2027       myTree.setCursor(Cursor.getDefaultCursor());
2028     }
2029   }
2030
2031   public static boolean isLoadingNode(final Object node) {
2032     return node instanceof LoadingNode;
2033   }
2034
2035   private AsyncResult<ArrayList<TreeNode>> collectNodesToInsert(final NodeDescriptor descriptor,
2036                                                                 final MutualMap<Object, Integer> elementToIndexMap,
2037                                                                 final DefaultMutableTreeNode parent,
2038                                                                 final boolean addLoadingNode,
2039                                                                 @NotNull final LoadedChildren loadedChildren) {
2040     final AsyncResult<ArrayList<TreeNode>> result = new AsyncResult<ArrayList<TreeNode>>();
2041
2042     final ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
2043     final Collection<Object> allElements = elementToIndexMap.getKeys();
2044
2045     final ActionCallback processingDone = new ActionCallback(allElements.size());
2046
2047     for (final Object child : allElements) {
2048       Integer index = elementToIndexMap.getValue(child);
2049       final Ref<NodeDescriptor> childDescr = new Ref<NodeDescriptor>(loadedChildren.getDescriptor(child));
2050       boolean needToUpdate = false;
2051       if (childDescr.get() == null) {
2052         childDescr.set(getTreeStructure().createDescriptor(child, descriptor));
2053         needToUpdate = true;
2054       }
2055
2056       if (childDescr.get() == null) {
2057         processingDone.setDone();
2058         continue;
2059       }
2060       childDescr.get().setIndex(index.intValue());
2061
2062       final ActionCallback update = new ActionCallback();
2063       if (needToUpdate) {
2064         update(childDescr.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
2065           public void run(Boolean changes) {
2066             loadedChildren.putDescriptor(child, childDescr.get(), changes);
2067             update.setDone();
2068           }
2069         });
2070       }
2071       else {
2072         update.setDone();
2073       }
2074
2075       update.doWhenDone(new Runnable() {
2076         public void run() {
2077           Object element = getElementFromDescriptor(childDescr.get());
2078           if (element == null) {
2079             processingDone.setDone();
2080           }
2081           else {
2082             DefaultMutableTreeNode node = getNodeForElement(element, false);
2083             if (node == null || node.getParent() != parent) {
2084               final DefaultMutableTreeNode childNode = createChildNode(childDescr.get());
2085               if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr.get())) {
2086                 insertLoadingNode(childNode, true);
2087               }
2088               else {
2089                 addToUnbuilt(childNode);
2090               }
2091               nodesToInsert.add(childNode);
2092               createMapping(element, childNode);
2093             }
2094             processingDone.setDone();
2095           }
2096         }
2097       });
2098     }
2099
2100     processingDone.doWhenDone(new Runnable() {
2101       public void run() {
2102         result.setDone(nodesToInsert);
2103       }
2104     });
2105
2106     return result;
2107   }
2108
2109   protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
2110     return new ElementNode(this, descriptor);
2111   }
2112
2113   protected boolean canYield() {
2114     return myCanYield && myYeildingUpdate.asBoolean();
2115   }
2116
2117   public long getClearOnHideDelay() {
2118     return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
2119   }
2120
2121   public ActionCallback getInitialized() {
2122     return myInitialized;
2123   }
2124
2125   public ActionCallback getReady(Object requestor) {
2126     return myBusyObject.getReady(requestor);
2127   }
2128
2129   private void addToUpdating(DefaultMutableTreeNode node) {
2130     synchronized (myUpdatingChildren) {
2131       myUpdatingChildren.add(node);
2132     }
2133   }
2134
2135   private void removeFromUpdating(DefaultMutableTreeNode node) {
2136     synchronized (myUpdatingChildren) {
2137       myUpdatingChildren.remove(node);
2138     }
2139   }
2140
2141   public boolean isUpdatingNow(DefaultMutableTreeNode node) {
2142     synchronized (myUpdatingChildren) {
2143       return myUpdatingChildren.contains(node);
2144     }
2145   }
2146
2147   boolean hasUpdatingNow() {
2148     synchronized (myUpdatingChildren) {
2149       return myUpdatingChildren.size() > 0;
2150     }
2151   }
2152
2153   public Map getNodeActions() {
2154     return myNodeActions;
2155   }
2156
2157   public List<Object> getLoadedChildrenFor(Object element) {
2158     List<Object> result = new ArrayList<Object>();
2159
2160     DefaultMutableTreeNode node = (DefaultMutableTreeNode)getNodeForElement(element, false);
2161     if (node != null) {
2162       for (int i = 0; i < node.getChildCount(); i++) {
2163         TreeNode each = node.getChildAt(i);
2164         if (isLoadingNode(each)) continue;
2165
2166         result.add(getElementFor(each));
2167       }
2168     }
2169
2170     return result;
2171   }
2172
2173   public boolean hasNodesToUpdate() {
2174     return getUpdater().hasNodesToUpdate();
2175   }
2176
2177   public List<Object> getExpandedElements() {
2178     List<Object> result = new ArrayList<Object>();
2179     Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode()));
2180     while (enumeration.hasMoreElements()) {
2181       TreePath each = enumeration.nextElement();
2182       Object eachElement = getElementFor(each.getLastPathComponent());
2183       if (eachElement != null) {
2184         result.add(eachElement);
2185       }
2186     }
2187
2188     return result;
2189   }
2190
2191   public ActionCallback cancelUpdate() {
2192     if (isReleased()) return new ActionCallback.Rejected();
2193
2194     setCancelRequested(true);
2195
2196     final ActionCallback done = new ActionCallback();
2197
2198     invokeLaterIfNeeded(new Runnable() {
2199       public void run() {
2200         if (isReleased()) {
2201           done.setRejected();
2202           return;
2203         }
2204
2205         if (myResettingToReadyNow.get()) {
2206           getReady(this).notify(done);
2207         } else if (isReady()) {
2208           resetToReadyNow();
2209           done.setDone();
2210         } else {
2211           if (isIdle() && hasPendingWork()) {
2212             resetToReadyNow();
2213             done.setDone();
2214           } else {
2215             getReady(this).notify(done);
2216           }
2217         }
2218
2219         maybeReady();
2220       }
2221     });
2222
2223     if (isEdt() || isPassthroughMode()) {
2224       maybeReady();
2225     }
2226
2227     return done;
2228   }
2229
2230   private void setCancelRequested(boolean requested) {
2231     try {
2232       if (isUnitTestingMode()) {
2233         acquireLock();
2234       } else {
2235         attemptLock();
2236       }
2237       myCancelRequest.set(requested);
2238     }
2239     catch (InterruptedException e) {
2240       return;
2241     }
2242     finally {
2243       releaseLock();
2244     }
2245   }
2246
2247   private boolean attemptLock() throws InterruptedException {
2248     return myStateLock.tryLock(Registry.intValue("ide.tree.uiLockAttempt"), TimeUnit.MILLISECONDS);
2249   }
2250
2251   private void acquireLock() throws InterruptedException {
2252     myStateLock.lock();
2253   }
2254
2255   private void releaseLock() {
2256     myStateLock.unlock();
2257   }
2258
2259
2260   public ActionCallback batch(final Progressive progressive) {
2261     assertIsDispatchThread();
2262
2263     EmptyProgressIndicator indicator = new EmptyProgressIndicator();
2264     final ActionCallback callback = new ActionCallback();
2265
2266     myBatchIndicators.put(progressive, indicator);
2267     myBatchCallbacks.put(progressive, callback);
2268
2269     try {
2270       progressive.run(indicator);
2271     } catch (ProcessCanceledException e) {
2272       resetToReadyNow().doWhenProcessed(new Runnable() {
2273         public void run() {
2274           callback.setRejected();
2275         }
2276       });
2277       return callback;
2278     }finally {
2279       if (isReleased()) return new ActionCallback.Rejected();
2280
2281       getReady(this).doWhenDone(new Runnable() {
2282         public void run() {
2283           if (myBatchIndicators.containsKey(progressive)) {
2284             ProgressIndicator indicator = myBatchIndicators.remove(progressive);
2285             myBatchCallbacks.remove(progressive);
2286
2287             if (indicator.isCanceled()) {
2288               callback.setRejected();
2289             } else {
2290               callback.setDone();
2291             }
2292           } else {
2293             callback.setRejected();
2294           }
2295         }
2296       });
2297
2298       maybeReady();
2299     }
2300
2301
2302     return callback;
2303   }
2304
2305   public boolean isCancelProcessed() {
2306     Boolean processed = checkValue(new Computable<Boolean>() {
2307       @Override
2308       public Boolean compute() {
2309         return Boolean.valueOf(myCancelRequest.get() || myResettingToReadyNow.get());
2310       }
2311     }, true, null);
2312
2313     return processed != null && processed.booleanValue();
2314   }
2315
2316   public boolean isToPaintSelection() {
2317     return isReady(true) || !mySelectionIsAdjusted;
2318   }
2319
2320   public boolean isReleaseRequested() {
2321     return myReleaseRequested;
2322   }
2323
2324   public void executeUserRunnable(Runnable runnable) {
2325     try {
2326       myUserRunnables.add(runnable);
2327       runnable.run();
2328     }
2329     finally {
2330       myUserRunnables.remove(runnable);
2331     }
2332   }
2333
2334   static class ElementNode extends DefaultMutableTreeNode {
2335
2336     Set<Object> myElements = new HashSet<Object>();
2337     AbstractTreeUi myUi;
2338
2339     ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
2340       super(descriptor);
2341       myUi = ui;
2342     }
2343
2344     @Override
2345     public void insert(final MutableTreeNode newChild, final int childIndex) {
2346       super.insert(newChild, childIndex);
2347       final Object element = myUi.getElementFor(newChild);
2348       if (element != null) {
2349         myElements.add(element);
2350       }
2351     }
2352
2353     @Override
2354     public void remove(final int childIndex) {
2355       final TreeNode node = getChildAt(childIndex);
2356       super.remove(childIndex);
2357       final Object element = myUi.getElementFor(node);
2358       if (element != null) {
2359         myElements.remove(element);
2360       }
2361     }
2362
2363     boolean isValidChild(Object childElement) {
2364       return myElements.contains(childElement);
2365     }
2366
2367     @Override
2368     public String toString() {
2369       return String.valueOf(getUserObject());
2370     }
2371   }
2372
2373   private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
2374     return getUpdatingParent(kid) != null;
2375   }
2376
2377   private DefaultMutableTreeNode getUpdatingParent(DefaultMutableTreeNode kid) {
2378     DefaultMutableTreeNode eachParent = kid;
2379     while (eachParent != null) {
2380       if (isUpdatingNow(eachParent)) return eachParent;
2381       eachParent = (DefaultMutableTreeNode)eachParent.getParent();
2382     }
2383
2384     return null;
2385   }
2386
2387   private boolean isLoadedInBackground(Object element) {
2388     return getLoadedInBackground(element) != null;
2389   }
2390
2391   private UpdateInfo getLoadedInBackground(Object element) {
2392     synchronized (myLoadedInBackground) {
2393       return myLoadedInBackground.get(element);
2394     }
2395   }
2396
2397   private void addToLoadedInBackground(Object element, UpdateInfo info) {
2398     synchronized (myLoadedInBackground) {
2399       myLoadedInBackground.put(element, info);
2400     }
2401   }
2402
2403   private void removeFromLoadedInBackground(final Object element) {
2404     synchronized (myLoadedInBackground) {
2405       myLoadedInBackground.remove(element);
2406     }
2407   }
2408
2409   private boolean isLoadingInBackgroundNow() {
2410     synchronized (myLoadedInBackground) {
2411       return myLoadedInBackground.size() > 0;
2412     }
2413   }
2414
2415   private boolean queueBackgroundUpdate(final UpdateInfo updateInfo, final DefaultMutableTreeNode node) {
2416     assertIsDispatchThread();
2417
2418     final Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor());
2419
2420     UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor);
2421     if (loaded != null) {
2422       loaded.apply(updateInfo);
2423       return false;
2424     }
2425
2426     addToLoadedInBackground(oldElementFromDescriptor, updateInfo);
2427
2428     if (!isNodeBeingBuilt(node)) {
2429       LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
2430       myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
2431     }
2432
2433     removeFromUnbuilt(node);
2434
2435     final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
2436     final Ref<Object> elementFromDescriptor = new Ref<Object>();
2437
2438     final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
2439
2440     final Runnable finalizeRunnable = new Runnable() {
2441       public void run() {
2442         invokeLaterIfNeeded(new Runnable() {
2443           public void run() {
2444             if (isReleased()) return;
2445
2446             removeLoading(node, false);
2447             removeFromLoadedInBackground(elementFromDescriptor.get());
2448             removeFromLoadedInBackground(oldElementFromDescriptor);
2449
2450             if (nodeToProcessActions[0] != null) {
2451               processNodeActionsIfReady(nodeToProcessActions[0]);
2452             }
2453           }
2454         });
2455       }
2456     };
2457
2458
2459     Runnable buildRunnable = new Runnable() {
2460       public void run() {
2461         if (updateInfo.getPass().isExpired()) {
2462           finalizeRunnable.run();
2463           return;
2464         }
2465
2466         if (!updateInfo.isDescriptorIsUpToDate()) {
2467           update(updateInfo.getDescriptor(), true);
2468         }
2469
2470         Object element = getElementFromDescriptor(updateInfo.getDescriptor());
2471         if (element == null) {
2472           removeFromLoadedInBackground(oldElementFromDescriptor);
2473           finalizeRunnable.run();
2474           return;
2475         }
2476
2477         elementFromDescriptor.set(element);
2478
2479         Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(updateInfo.getDescriptor()));
2480
2481         final LoadedChildren loaded = new LoadedChildren(loadedElements);
2482         for (final Object each : loadedElements) {
2483           final NodeDescriptor eachChildDescriptor = getTreeStructure().createDescriptor(each, updateInfo.getDescriptor());
2484           execute(new Runnable() {
2485             public void run() {
2486               loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).getResult());
2487             }
2488           });
2489         }
2490
2491         children.set(loaded);
2492
2493       }
2494
2495       @Override
2496       public String toString() {
2497         return "runnable=" + oldElementFromDescriptor;
2498       }
2499     };
2500
2501     Runnable updateRunnable = new Runnable() {
2502       public void run() {
2503         if (updateInfo.getPass().isExpired()) {
2504           finalizeRunnable.run();
2505           return;
2506         }
2507
2508         if (children.get() == null) {
2509           finalizeRunnable.run();
2510           return;
2511         }
2512
2513         if (isRerunNeeded(updateInfo.getPass())) {
2514           removeFromLoadedInBackground(elementFromDescriptor.get());
2515           getUpdater().addSubtreeToUpdate(updateInfo.getPass());
2516           return;
2517         }
2518
2519         removeFromLoadedInBackground(elementFromDescriptor.get());
2520
2521         if (myUnbuiltNodes.contains(node)) {
2522           Pair<Boolean, LoadedChildren> unbuilt =
2523             processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()),
2524                            children.get());
2525           if (unbuilt.getFirst()) {
2526             nodeToProcessActions[0] = node;
2527             return;
2528           }
2529         }
2530
2531         updateNodeChildren(node, updateInfo.getPass(), children.get(), true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(),
2532                            true);
2533
2534
2535         if (isRerunNeeded(updateInfo.getPass())) {
2536           getUpdater().addSubtreeToUpdate(updateInfo.getPass());
2537           return;
2538         }
2539
2540         Object element = elementFromDescriptor.get();
2541
2542         if (element != null) {
2543           removeLoading(node, false);
2544           nodeToProcessActions[0] = node;
2545         }
2546       }
2547     };
2548     queueToBackground(buildRunnable, updateRunnable, node).doWhenProcessed(finalizeRunnable).doWhenRejected(new Runnable() {
2549       public void run() {
2550         updateInfo.getPass().expire();
2551       }
2552     });
2553
2554     return true;
2555   }
2556
2557   private boolean isExpanded(DefaultMutableTreeNode node, boolean isExpanded) {
2558     return isExpanded || myTree.isExpanded(getPathFor(node));
2559   }
2560
2561   private void removeLoading(DefaultMutableTreeNode parent, boolean forced) {
2562     if (!forced && myUnbuiltNodes.contains(parent) && !myCancelledBuild.containsKey(parent)) {
2563       return;
2564     }
2565
2566     for (int i = 0; i < parent.getChildCount(); i++) {
2567       TreeNode child = parent.getChildAt(i);
2568       if (removeIfLoading(child)) {
2569         i--;
2570       }
2571     }
2572
2573     if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
2574       insertLoadingNode(parent, false);
2575     }
2576
2577     maybeReady();
2578   }
2579
2580   private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
2581     assertIsDispatchThread();
2582
2583     if (isNodeBeingBuilt(node)) return;
2584
2585     final Object o = node.getUserObject();
2586     if (!(o instanceof NodeDescriptor)) return;
2587
2588
2589     if (isYeildingNow()) {
2590       myPendingNodeActions.add(node);
2591       return;
2592     }
2593
2594     final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
2595
2596     boolean childrenReady = !isLoadedInBackground(element);
2597
2598     processActions(node, element, myNodeActions, childrenReady ? myNodeChildrenActions : null);
2599     if (childrenReady) {
2600       processActions(node, element, myNodeChildrenActions, null);
2601     }
2602
2603     if (!isUpdatingParent(node) && !isWorkerBusy()) {
2604       final UpdaterTreeState state = myUpdaterState;
2605       if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
2606         if (canInitiateNewActivity()) {
2607           if (!state.restore(childrenReady ? node : null)) {
2608             setUpdaterState(state);
2609           }
2610         }
2611       }
2612     }
2613
2614     maybeReady();
2615   }
2616
2617
2618   private void processActions(DefaultMutableTreeNode node,
2619                               Object element,
2620                               final Map<Object, List<NodeAction>> nodeActions,
2621                               @Nullable final Map<Object, List<NodeAction>> secondaryNodeAction) {
2622     final List<NodeAction> actions = nodeActions.get(element);
2623     if (actions != null) {
2624       nodeActions.remove(element);
2625
2626       List<NodeAction> secondary = secondaryNodeAction != null ? secondaryNodeAction.get(element) : null;
2627       for (NodeAction each : actions) {
2628         if (secondary != null && secondary.contains(each)) {
2629           secondary.remove(each);
2630         }
2631         each.onReady(node);
2632       }
2633     }
2634   }
2635
2636
2637   private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) {
2638     if (!getBuilder().isSmartExpand()) return false;
2639
2640     boolean smartExpand = !myNotForSmartExpand.contains(node) && canSmartExpand;
2641     return smartExpand ? validateAutoExpand(smartExpand, getElementFor(node)) : false;
2642   }
2643
2644   private void processSmartExpand(final DefaultMutableTreeNode node, final boolean canSmartExpand, boolean forced) {
2645     if (!getBuilder().isSmartExpand()) return;
2646
2647     boolean can = canSmartExpand(node, canSmartExpand);
2648
2649     if (!can && !forced) return;
2650
2651     if (isNodeBeingBuilt(node) && !forced) {
2652       addNodeAction(getElementFor(node), new NodeAction() {
2653         public void onReady(DefaultMutableTreeNode node) {
2654           processSmartExpand(node, canSmartExpand, true);
2655         }
2656       }, true);
2657     }
2658     else {
2659       TreeNode child = getChildForSmartExpand(node);
2660       if (child != null) {
2661         final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child);
2662         processInnerChange(new Runnable() {
2663           public void run() {
2664             myTree.expandPath(childPath);
2665           }
2666         });
2667       }
2668     }
2669   }
2670
2671   @Nullable
2672   private TreeNode getChildForSmartExpand(DefaultMutableTreeNode node) {
2673     int realChildCount = 0;
2674     TreeNode nodeToExpand = null;
2675
2676     for (int i = 0; i < node.getChildCount(); i++) {
2677       TreeNode eachChild = node.getChildAt(i);
2678
2679       if (!isLoadingNode(eachChild)) {
2680         realChildCount++;
2681         if (nodeToExpand == null) {
2682           nodeToExpand = eachChild;
2683         }
2684       }
2685
2686       if (realChildCount > 1) {
2687         nodeToExpand = null;
2688         break;
2689       }
2690     }
2691
2692     return nodeToExpand;
2693   }
2694
2695   public boolean isLoadingChildrenFor(final Object nodeObject) {
2696     if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
2697
2698     DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
2699
2700     int loadingNodes = 0;
2701     for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
2702       TreeNode child = node.getChildAt(i);
2703       if (isLoadingNode(child)) {
2704         loadingNodes++;
2705       }
2706     }
2707     return loadingNodes > 0 && loadingNodes == node.getChildCount();
2708   }
2709
2710   private boolean isParentLoading(Object nodeObject) {
2711     return getParentLoading(nodeObject) != null;
2712   }
2713
2714   private DefaultMutableTreeNode getParentLoading(Object nodeObject) {
2715     if (!(nodeObject instanceof DefaultMutableTreeNode)) return null;
2716
2717     DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
2718
2719     TreeNode eachParent = node.getParent();
2720
2721     while (eachParent != null) {
2722       eachParent = eachParent.getParent();
2723       if (eachParent instanceof DefaultMutableTreeNode) {
2724         final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
2725         if (isLoadedInBackground(eachElement)) return (DefaultMutableTreeNode)eachParent;
2726       }
2727     }
2728
2729     return null;
2730   }
2731
2732   protected String getLoadingNodeText() {
2733     return IdeBundle.message("progress.searching");
2734   }
2735
2736   private ActionCallback processExistingNode(final DefaultMutableTreeNode childNode,
2737                                              final NodeDescriptor childDescriptor,
2738                                              final DefaultMutableTreeNode parentNode,
2739                                              final MutualMap<Object, Integer> elementToIndexMap,
2740                                              final TreeUpdatePass pass,
2741                                              final boolean canSmartExpand,
2742                                              final boolean forceUpdate,
2743                                              LoadedChildren parentPreloadedChildren) {
2744
2745     final ActionCallback result = new ActionCallback();
2746
2747     if (pass.isExpired()) {
2748       return new ActionCallback.Rejected();
2749     }
2750
2751     final Ref<NodeDescriptor> childDesc = new Ref<NodeDescriptor>(childDescriptor);
2752
2753     if (childDesc.get() == null) {
2754       pass.expire();
2755       return new ActionCallback.Rejected();
2756     }
2757     final Object oldElement = getElementFromDescriptor(childDesc.get());
2758     if (oldElement == null) {
2759       pass.expire();
2760       return new ActionCallback.Rejected();
2761     }
2762
2763     AsyncResult<Boolean> update = new AsyncResult<Boolean>();
2764     if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) {
2765       update.setDone(parentPreloadedChildren.isUpdated(oldElement));
2766     }
2767     else {
2768       update = update(childDesc.get(), false);
2769     }
2770
2771     update.doWhenDone(new AsyncResult.Handler<Boolean>() {
2772       public void run(Boolean isChanged) {
2773         final Ref<Boolean> changes = new Ref<Boolean>(isChanged);
2774
2775         final Ref<Boolean> forceRemapping = new Ref<Boolean>(false);
2776         final Ref<Object> newElement = new Ref<Object>(getElementFromDescriptor(childDesc.get()));
2777
2778         final Integer index =
2779           newElement.get() != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc.get())) : null;
2780         final AsyncResult<Boolean> updateIndexDone = new AsyncResult<Boolean>();
2781         final ActionCallback indexReady = new ActionCallback();
2782         if (index != null) {
2783           final Object elementFromMap = elementToIndexMap.getKey(index);
2784           if (elementFromMap != newElement.get() && elementFromMap.equals(newElement.get())) {
2785             if (isInStructure(elementFromMap) && isInStructure(newElement.get())) {
2786               if (parentNode.getUserObject() instanceof NodeDescriptor) {
2787                 final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
2788                 childDesc.set(getTreeStructure().createDescriptor(elementFromMap, parentDescriptor));
2789                 childNode.setUserObject(childDesc.get());
2790                 newElement.set(elementFromMap);
2791                 forceRemapping.set(true);
2792                 update(childDesc.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
2793                   public void run(Boolean isChanged) {
2794                     changes.set(isChanged);
2795                     updateIndexDone.setDone(isChanged);
2796                   }
2797                 });
2798               }
2799             }
2800             else {
2801               updateIndexDone.setDone(changes.get());
2802             }
2803           }
2804           else {
2805             updateIndexDone.setDone(changes.get());
2806           }
2807
2808           updateIndexDone.doWhenDone(new Runnable() {
2809             public void run() {
2810               if (childDesc.get().getIndex() != index.intValue()) {
2811                 changes.set(true);
2812               }
2813               childDesc.get().setIndex(index.intValue());
2814               indexReady.setDone();
2815             }
2816           });
2817         }
2818         else {
2819           updateIndexDone.setDone();
2820         }
2821
2822         updateIndexDone.doWhenDone(new Runnable() {
2823           public void run() {
2824             if (index != null && changes.get()) {
2825               updateNodeImageAndPosition(childNode, false);
2826             }
2827             if (!oldElement.equals(newElement.get()) | forceRemapping.get()) {
2828               removeMapping(oldElement, childNode, newElement.get());
2829               if (newElement.get() != null) {
2830                 createMapping(newElement.get(), childNode);
2831               }
2832               NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
2833               if (parentDescriptor != null) {
2834                 parentDescriptor.setChildrenSortingStamp(-1);
2835               }
2836             }
2837
2838             if (index == null) {
2839               int selectedIndex = -1;
2840               if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
2841                 selectedIndex = parentNode.getIndex(childNode);
2842               }
2843
2844               if (childNode.getParent() instanceof DefaultMutableTreeNode) {
2845                 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
2846                 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
2847                   if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
2848                     insertLoadingNode(parent, false);
2849                   }
2850                 }
2851               }
2852
2853               Object disposedElement = getElementFor(childNode);
2854
2855               removeNodeFromParent(childNode, selectedIndex >= 0);
2856               disposeNode(childNode);
2857
2858               adjustSelectionOnChildRemove(parentNode, selectedIndex, disposedElement);
2859             }
2860             else {
2861               elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc.get()));
2862               updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
2863             }
2864
2865             if (parentNode.equals(getRootNode())) {
2866               myTreeModel.nodeChanged(getRootNode());
2867             }
2868
2869             result.setDone();
2870           }
2871         });
2872       }
2873     });
2874
2875
2876     return result;
2877   }
2878
2879   private void adjustSelectionOnChildRemove(DefaultMutableTreeNode parentNode, int selectedIndex, Object disposedElement) {
2880     DefaultMutableTreeNode node = getNodeForElement(disposedElement, false);
2881     if (node != null && isValidForSelectionAdjusting(node)) {
2882       Object newElement = getElementFor(node);
2883       addSelectionPath(getPathFor(node), true, getExpiredElementCondition(newElement), disposedElement);