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