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