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