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