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