TreeUi: tests refactored
[idea/community.git] / platform / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUi.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ide.util.treeView;
17
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.openapi.application.Application;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.progress.ProcessCanceledException;
23 import com.intellij.openapi.progress.ProgressIndicator;
24 import com.intellij.openapi.progress.ProgressManager;
25 import com.intellij.openapi.project.IndexNotReadyException;
26 import com.intellij.openapi.util.*;
27 import com.intellij.openapi.util.registry.Registry;
28 import com.intellij.openapi.util.registry.RegistryValue;
29 import com.intellij.ui.LoadingNode;
30 import com.intellij.ui.treeStructure.Tree;
31 import com.intellij.util.Alarm;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.ConcurrencyUtil;
34 import com.intellij.util.Time;
35 import com.intellij.util.concurrency.WorkerThread;
36 import com.intellij.util.containers.HashSet;
37 import com.intellij.util.enumeration.EnumerationCopy;
38 import com.intellij.util.ui.UIUtil;
39 import com.intellij.util.ui.tree.TreeUtil;
40 import com.intellij.util.ui.update.Activatable;
41 import com.intellij.util.ui.update.UiNotifyConnector;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import javax.swing.*;
46 import javax.swing.event.TreeExpansionEvent;
47 import javax.swing.event.TreeExpansionListener;
48 import javax.swing.event.TreeSelectionEvent;
49 import javax.swing.event.TreeSelectionListener;
50 import javax.swing.tree.*;
51 import java.awt.*;
52 import java.awt.event.FocusAdapter;
53 import java.awt.event.FocusEvent;
54 import java.security.AccessControlException;
55 import java.util.*;
56 import java.util.List;
57 import java.util.concurrent.ScheduledExecutorService;
58 import java.util.concurrent.TimeUnit;
59
60 public class AbstractTreeUi {
61   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
62   protected JTree myTree;// protected for TestNG
63   @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel;
64   private AbstractTreeStructure myTreeStructure;
65   private AbstractTreeUpdater myUpdater;
66
67   private Comparator<NodeDescriptor> myNodeDescriptorComparator;
68   private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() {
69     public int compare(TreeNode n1, TreeNode n2) {
70       if (isLoadingNode(n1) || isLoadingNode(n2)) return 0;
71       NodeDescriptor nodeDescriptor1 = getDescriptorFrom(((DefaultMutableTreeNode)n1));
72       NodeDescriptor nodeDescriptor2 = getDescriptorFrom(((DefaultMutableTreeNode)n2));
73       return myNodeDescriptorComparator != null
74              ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2)
75              : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex();
76     }
77   };
78   long myOwnComparatorStamp;
79   long myLastComparatorStamp;
80
81   private DefaultMutableTreeNode myRootNode;
82   private final HashMap<Object, Object> myElementToNodeMap = new HashMap<Object, Object>();
83   private final HashSet<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>();
84   private TreeExpansionListener myExpansionListener;
85   private MySelectionListener mySelectionListener;
86
87   private WorkerThread myWorker = null;
88   private final Set<Runnable> myActiveWorkerTasks = new HashSet<Runnable>();
89
90   private ProgressIndicator myProgress;
91   private static final int WAIT_CURSOR_DELAY = 100;
92   private AbstractTreeNode<Object> TREE_NODE_WRAPPER;
93   private boolean myRootNodeWasInitialized = false;
94   private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>();
95   private boolean myUpdateFromRootRequested;
96   private boolean myWasEverShown;
97   private boolean myUpdateIfInactive;
98
99   private final Map<Object, UpdateInfo> myLoadedInBackground = new HashMap<Object, UpdateInfo>();
100   private final Map<Object, List<NodeAction>> myNodeChildrenActions = new HashMap<Object, List<NodeAction>>();
101
102   private long myClearOnHideDelay = -1;
103   private ScheduledExecutorService ourClearanceService;
104   private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
105
106   private final Set<Runnable> myDeferredSelections = new HashSet<Runnable>();
107   private final Set<Runnable> myDeferredExpansions = new HashSet<Runnable>();
108
109   private boolean myCanProcessDeferredSelections;
110
111   private UpdaterTreeState myUpdaterState;
112   private AbstractTreeBuilder myBuilder;
113
114   private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
115   private long myJanitorPollPeriod = Time.SECOND * 10;
116   private boolean myCheckStructure = false;
117
118
119   private boolean myCanYield = false;
120
121   private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
122
123   private boolean myYeildingNow;
124
125   private final Set<DefaultMutableTreeNode> myPendingNodeActions = new HashSet<DefaultMutableTreeNode>();
126   private final Set<Runnable> myYeildingDoneRunnables = new HashSet<Runnable>();
127
128   private final Alarm myBusyAlarm = new Alarm();
129   private final Runnable myWaiterForReady = new Runnable() {
130     public void run() {
131       maybeSetBusyAndScheduleWaiterForReady(false);
132     }
133   };
134
135   private final RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
136   private final RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
137   private final RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimout");
138
139   private boolean myWasEverIndexNotReady;
140   private boolean myShowing;
141   private FocusAdapter myFocusListener = new FocusAdapter() {
142     @Override
143     public void focusGained(FocusEvent e) {
144       maybeReady();
145     }
146   };
147   private Set<DefaultMutableTreeNode> myNotForSmartExpand = new HashSet<DefaultMutableTreeNode>();
148   private TreePath myRequestedExpand;
149   private ActionCallback myInitialized = new ActionCallback();
150   private Map<Object, ActionCallback> myReadyCallbacks = new WeakHashMap<Object, ActionCallback>();
151
152   private boolean myPassthroughMode = false;
153
154
155   protected void init(AbstractTreeBuilder builder,
156                       JTree tree,
157                       DefaultTreeModel treeModel,
158                       AbstractTreeStructure treeStructure,
159                       @Nullable Comparator<NodeDescriptor> comparator,
160                       boolean updateIfInactive) {
161     myBuilder = builder;
162     myTree = tree;
163     myTreeModel = treeModel;
164     TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
165     myTree.setModel(myTreeModel);
166     setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
167     setTreeStructure(treeStructure);
168     myNodeDescriptorComparator = comparator;
169     myUpdateIfInactive = updateIfInactive;
170
171     myExpansionListener = new MyExpansionListener();
172     myTree.addTreeExpansionListener(myExpansionListener);
173
174     mySelectionListener = new MySelectionListener();
175     myTree.addTreeSelectionListener(mySelectionListener);
176
177     setUpdater(getBuilder().createUpdater());
178     myProgress = getBuilder().createProgressIndicator();
179     Disposer.register(getBuilder(), getUpdater());
180
181     final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
182       public void showNotify() {
183         myShowing = true;
184         myWasEverShown = true;
185         if (!isReleased()) {
186           activate(true);
187         }
188       }
189
190       public void hideNotify() {
191         myShowing = false;
192         if (!isReleased()) {
193           deactivate();
194         }
195       }
196     });
197     Disposer.register(getBuilder(), uiNotify);
198
199     myTree.addFocusListener(myFocusListener);
200   }
201
202
203   boolean isNodeActionsPending() {
204     return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty();
205   }
206
207   private void clearNodeActions() {
208     myNodeActions.clear();
209     myNodeChildrenActions.clear();
210   }
211
212   private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
213     if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
214
215     if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
216       final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
217       final boolean isBusy = !isReady() || forcedBusy;
218       if (isBusy && tree.isShowing()) {
219         tree.setPaintBusy(true);
220         myBusyAlarm.cancelAllRequests();
221         myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
222       }
223       else {
224         tree.setPaintBusy(false);
225       }
226     }
227   }
228
229   private void initClearanceServiceIfNeeded() {
230     if (ourClearanceService != null) return;
231
232     ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor");
233     ourClearanceService.scheduleWithFixedDelay(new Runnable() {
234       public void run() {
235         cleanUpAll();
236       }
237     }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
238   }
239
240   private void cleanUpAll() {
241     final long now = System.currentTimeMillis();
242     final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
243     for (AbstractTreeUi eachUi : uis) {
244       if (eachUi == null) continue;
245       final Long timeToCleanup = ourUi2Countdown.get(eachUi);
246       if (timeToCleanup == null) continue;
247       if (now >= timeToCleanup.longValue()) {
248         ourUi2Countdown.remove(eachUi);
249         Runnable runnable = new Runnable() {
250           public void run() {
251             getBuilder().cleanUp();
252           }
253         };
254         if (isPassthroughMode()) {
255           runnable.run();
256         } else {
257           UIUtil.invokeAndWaitIfNeeded(runnable);
258         }
259       }
260     }
261   }
262
263   protected void doCleanUp() {
264     Runnable cleanup = new Runnable() {
265       public void run() {
266         if (!isReleased()) {
267           cleanUpNow();
268         }
269       }
270     };
271
272     if (isPassthroughMode()) {
273       cleanup.run();
274     } else {
275       UIUtil.invokeLaterIfNeeded(cleanup);
276     }
277   }
278
279   private void disposeClearanceService() {
280     try {
281       if (ourClearanceService != null) {
282         ourClearanceService.shutdown();
283         ourClearanceService = null;
284       }
285     }
286     catch (AccessControlException e) {
287       LOG.warn(e);
288     }
289   }
290
291   public void activate(boolean byShowing) {
292     myCanProcessDeferredSelections = true;
293     ourUi2Countdown.remove(this);
294
295     if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) {
296       getBuilder().updateFromRoot();
297     }
298
299     getUpdater().showNotify();
300
301     myWasEverShown |= byShowing;
302   }
303
304   public void deactivate() {
305     getUpdater().hideNotify();
306     myBusyAlarm.cancelAllRequests();
307
308     if (!myWasEverShown) return;
309
310     if (isNodeActionsPending()) {
311       cancelBackgroundLoading();
312       myUpdateFromRootRequested = true;
313     }
314
315     if (getClearOnHideDelay() >= 0) {
316       ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
317       initClearanceServiceIfNeeded();
318     }
319   }
320
321
322   public void release() {
323     if (isReleased()) return;
324
325     myTree.removeTreeExpansionListener(myExpansionListener);
326     myTree.removeTreeSelectionListener(mySelectionListener);
327     myTree.removeFocusListener(myFocusListener);
328
329     disposeNode(getRootNode());
330     myElementToNodeMap.clear();
331     getUpdater().cancelAllRequests();
332     if (myWorker != null) {
333       myWorker.dispose(true);
334       clearWorkerTasks();
335     }
336     TREE_NODE_WRAPPER.setValue(null);
337     if (myProgress != null) {
338       myProgress.cancel();
339     }
340     disposeClearanceService();
341
342     myTree = null;
343     setUpdater(null);
344     myWorker = null;
345 //todo [kirillk] afraid to do so just in release day, to uncomment
346 //    myTreeStructure = null;
347     myBuilder = null;
348
349     clearNodeActions();
350
351     myDeferredSelections.clear();
352     myDeferredExpansions.clear();
353     myYeildingDoneRunnables.clear();
354   }
355
356   public boolean isReleased() {
357     return myBuilder == null;
358   }
359
360   protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
361     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     getTreeStructure().commit();
832     final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node);
833     final boolean wasLeaf = node.getChildCount() == 0;
834
835     try {
836       final NodeDescriptor descriptor = getDescriptorFrom(node);
837       if (descriptor == null) {
838         removeLoading(node, true);
839         return;
840       }
841
842       boolean bgBuild = isToBuildInBackground(descriptor);
843       boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded;
844
845       if (notRequiredToUpdateChildren && forceUpdate && !wasExpanded) {
846         boolean alwaysPlus = getBuilder().isAlwaysShowPlus(descriptor);
847         if (alwaysPlus && wasLeaf) {
848           notRequiredToUpdateChildren = false;
849         } else {
850           notRequiredToUpdateChildren = alwaysPlus;
851         }
852       }
853
854       final Ref<LoadedChildren> preloaded = new Ref<LoadedChildren>(loadedChildren);
855       boolean descriptorWasUpdated = descriptorIsUpToDate;
856
857       if (notRequiredToUpdateChildren) {
858         if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) {
859           insertLoadingNode(node, true);
860         }
861         return;
862       }
863
864       if (!forcedNow) {
865         if (!bgBuild) {
866           if (myUnbuiltNodes.contains(node)) {
867             if (!descriptorWasUpdated) {
868               update(descriptor, true);
869               descriptorWasUpdated = true;
870             }
871
872             if (processAlwaysLeaf(node)) return;
873
874             Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null);
875             if (unbuilt.getFirst()) return;
876             preloaded.set(unbuilt.getSecond());
877           }
878         }
879       }
880
881
882       final boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded);
883
884       if (!forcedNow && isToBuildInBackground(descriptor)) {
885         if (processAlwaysLeaf(node)) return;
886
887         queueBackgroundUpdate(
888           new UpdateInfo(descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorWasUpdated), node);
889         return;
890       }
891       else {
892         if (!descriptorWasUpdated) {
893           update(descriptor, false).doWhenDone(new Runnable() {
894             public void run() {
895               if (processAlwaysLeaf(node)) return;
896               updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
897             }
898           });
899         }
900         else {
901           if (processAlwaysLeaf(node)) return;
902
903           updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
904         }
905       }
906     }
907     finally {
908       processNodeActionsIfReady(node);
909     }
910   }
911
912   private boolean processAlwaysLeaf(DefaultMutableTreeNode node) {
913     Object element = getElementFor(node);
914     if (getTreeStructure().isAlwaysLeaf(element)) {
915       removeLoading(node, true);
916       processNodeActionsIfReady(node);
917       return true;
918     } else {
919       return false;
920     }
921   }
922
923   private boolean isChildNodeForceUpdate(DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) {
924     TreePath path = getPathFor(node);
925     return parentForceUpdate && (parentExpanded || myTree.isExpanded(path));
926   }
927
928   private void updateNodeChildrenNow(final DefaultMutableTreeNode node,
929                                      final TreeUpdatePass pass,
930                                      final LoadedChildren preloadedChildren,
931                                      final boolean toSmartExpand,
932                                      final boolean wasExpanded,
933                                      final boolean wasLeaf,
934                                      final boolean forceUpdate) {
935     final NodeDescriptor descriptor = getDescriptorFrom(node);
936
937     final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren);
938     final LoadedChildren loadedChildren =
939       preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray());
940
941
942     addToUpdating(node);
943     pass.setCurrentNode(node);
944
945     final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
946
947     processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren)
948       .doWhenDone(new Runnable() {
949         public void run() {
950           if (isDisposed(node)) {
951             return;
952           }
953
954           removeLoading(node, false);
955
956           final boolean expanded = isExpanded(node, wasExpanded);
957
958           collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren)
959             .doWhenDone(new AsyncResult.Handler<ArrayList<TreeNode>>() {
960               public void run(ArrayList<TreeNode> nodesToInsert) {
961                 insertNodesInto(nodesToInsert, node);
962                 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
963                 removeLoading(node, true);
964                 removeFromUpdating(node);
965
966                 if (node.getChildCount() > 0) {
967                   if (expanded) {
968                     expand(node, canSmartExpand);
969                   }
970                 }
971
972                 final Object element = getElementFor(node);
973                 addNodeAction(element, new NodeAction() {
974                   public void onReady(final DefaultMutableTreeNode node) {
975                     removeLoading(node, false);
976                   }
977                 }, false);
978
979                 processNodeActionsIfReady(node);
980               }
981             });
982         }
983       });
984   }
985
986   private boolean isDisposed(DefaultMutableTreeNode node) {
987     return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
988   }
989
990   private void expand(DefaultMutableTreeNode node, boolean canSmartExpand) {
991     expand(new TreePath(node.getPath()), canSmartExpand);
992   }
993
994   private void expand(final TreePath path, boolean canSmartExpand) {
995     if (path == null) return;
996
997
998     final Object last = path.getLastPathComponent();
999     boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
1000     final boolean isRoot = last == myTree.getModel().getRoot();
1001     final TreePath parent = path.getParentPath();
1002     if (isRoot && !myTree.isExpanded(path)) {
1003       if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) {
1004         insertLoadingNode((DefaultMutableTreeNode)last, false);
1005       }
1006       expandPath(path, canSmartExpand);
1007     }
1008     else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last))) {
1009       if (last instanceof DefaultMutableTreeNode) {
1010         processNodeActionsIfReady((DefaultMutableTreeNode)last);
1011       }
1012     }
1013     else {
1014       if (isLeaf && myUnbuiltNodes.contains(last)) {
1015         insertLoadingNode((DefaultMutableTreeNode)last, true);
1016         expandPath(path, canSmartExpand);
1017       }
1018       else if (isLeaf && parent != null) {
1019         final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
1020         if (parentNode != null) {
1021           addToUnbuilt(parentNode);
1022         }
1023         expandPath(parent, canSmartExpand);
1024       }
1025       else {
1026         expandPath(path, canSmartExpand);
1027       }
1028     }
1029   }
1030
1031   private void addToUnbuilt(DefaultMutableTreeNode node) {
1032     myUnbuiltNodes.add(node);
1033   }
1034
1035   private void removeFromUnbuilt(DefaultMutableTreeNode node) {
1036     myUnbuiltNodes.remove(node);
1037   }
1038
1039   private Pair<Boolean, LoadedChildren> processUnbuilt(final DefaultMutableTreeNode node,
1040                                                        final NodeDescriptor descriptor,
1041                                                        final TreeUpdatePass pass,
1042                                                        boolean isExpanded,
1043                                                        final LoadedChildren loadedChildren) {
1044     if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) {
1045       return new Pair<Boolean, LoadedChildren>(true, null);
1046     }
1047
1048     final Object element = getElementFor(node);
1049
1050     final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element));
1051
1052     boolean processed;
1053
1054     if (children.getElements().size() == 0) {
1055       removeLoading(node, true);
1056       processed = true;
1057     }
1058     else {
1059       if (isAutoExpand(node)) {
1060         addNodeAction(getElementFor(node), new NodeAction() {
1061           public void onReady(final DefaultMutableTreeNode node) {
1062             final TreePath path = new TreePath(node.getPath());
1063             if (getTree().isExpanded(path) || children.getElements().size() == 0) {
1064               removeLoading(node, false);
1065             }
1066             else {
1067               maybeYeild(new ActiveRunnable() {
1068                 public ActionCallback run() {
1069                   expand(element, null);
1070                   return new ActionCallback.Done();
1071                 }
1072               }, pass, node);
1073             }
1074           }
1075         }, false);
1076       }
1077       processed = false;
1078     }
1079
1080     processNodeActionsIfReady(node);
1081
1082     return new Pair<Boolean, LoadedChildren>(processed, children);
1083   }
1084
1085   private boolean removeIfLoading(TreeNode node) {
1086     if (isLoadingNode(node)) {
1087       moveSelectionToParentIfNeeded(node);
1088       removeNodeFromParent((MutableTreeNode)node, false);
1089       return true;
1090     }
1091
1092     return false;
1093   }
1094
1095   private void moveSelectionToParentIfNeeded(TreeNode node) {
1096     TreePath path = getPathFor(node);
1097     if (myTree.getSelectionModel().isPathSelected(path)) {
1098       TreePath parentPath = path.getParentPath();
1099       myTree.getSelectionModel().removeSelectionPath(path);
1100       if (parentPath != null) {
1101         myTree.getSelectionModel().addSelectionPath(parentPath);
1102       }
1103     }
1104   }
1105
1106   //todo [kirillk] temporary consistency check
1107   private Object[] getChildrenFor(final Object element) {
1108     final Object[] passOne;
1109     try {
1110       passOne = getTreeStructure().getChildElements(element);
1111     }
1112     catch (IndexNotReadyException e) {
1113       if (!myWasEverIndexNotReady) {
1114         myWasEverIndexNotReady = true;
1115         LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
1116       }
1117       return ArrayUtil.EMPTY_OBJECT_ARRAY;
1118     }
1119
1120     if (!myCheckStructure) return passOne;
1121
1122     final Object[] passTwo = getTreeStructure().getChildElements(element);
1123
1124     final HashSet two = new HashSet(Arrays.asList(passTwo));
1125
1126     if (passOne.length != passTwo.length) {
1127       LOG.error(
1128         "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1129         element);
1130     }
1131     else {
1132       for (Object eachInOne : passOne) {
1133         if (!two.contains(eachInOne)) {
1134           LOG.error(
1135             "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1136             element);
1137           break;
1138         }
1139       }
1140     }
1141
1142     return passOne;
1143   }
1144
1145   private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert,
1146                                    TreeUpdatePass pass,
1147                                    boolean canSmartExpand,
1148                                    boolean forceUpdate) {
1149     for (TreeNode aNodesToInsert : nodesToInsert) {
1150       DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
1151       updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
1152     }
1153   }
1154
1155   private ActionCallback processExistingNodes(final DefaultMutableTreeNode node,
1156                                               final MutualMap<Object, Integer> elementToIndexMap,
1157                                               final TreeUpdatePass pass,
1158                                               final boolean canSmartExpand,
1159                                               final boolean forceUpdate,
1160                                               final boolean wasExpaned,
1161                                               final LoadedChildren preloaded) {
1162
1163     final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
1164     return maybeYeild(new ActiveRunnable() {
1165       public ActionCallback run() {
1166         if (pass.isExpired()) return new ActionCallback.Rejected();
1167         if (childNodes.size() == 0) return new ActionCallback.Done();
1168
1169
1170         final ActionCallback result = new ActionCallback(childNodes.size());
1171
1172         for (TreeNode each : childNodes) {
1173           final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each;
1174           if (isLoadingNode(eachChild)) {
1175             result.setDone();
1176             continue;
1177           }
1178
1179           final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned);
1180
1181           maybeYeild(new ActiveRunnable() {
1182             @Override
1183             public ActionCallback run() {
1184               return processExistingNode(eachChild, getDescriptorFrom(eachChild), node, elementToIndexMap, pass, canSmartExpand,
1185                                          childForceUpdate, preloaded);
1186             }
1187           }, pass, node).notify(result);
1188
1189           if (result.isRejected()) {
1190             break;
1191           }
1192         }
1193
1194         return result;
1195       }
1196     }, pass, node);
1197   }
1198
1199   private boolean isRerunNeeded(TreeUpdatePass pass) {
1200     if (pass.isExpired()) return false;
1201
1202     final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
1203
1204     return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
1205   }
1206
1207   private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
1208     final ActionCallback result = new ActionCallback();
1209
1210     if (isRerunNeeded(pass)) {
1211       getUpdater().addSubtreeToUpdate(pass);
1212       result.setRejected();
1213     }
1214     else {
1215       if (isToYieldUpdateFor(node)) {
1216         pass.setCurrentNode(node);
1217         yieldAndRun(new Runnable() {
1218           public void run() {
1219             if (pass.isExpired()) return;
1220
1221             if (isRerunNeeded(pass)) {
1222               runDone(new Runnable() {
1223                 public void run() {
1224                   if (!pass.isExpired()) {
1225                     getUpdater().addSubtreeToUpdate(pass);
1226                   }
1227                 }
1228               });
1229               result.setRejected();
1230             }
1231             else {
1232               processRunnable.run().notify(result);
1233             }
1234           }
1235         }, pass);
1236       }
1237       else {
1238         processRunnable.run().notify(result);
1239       }
1240     }
1241
1242     return result;
1243   }
1244
1245   private void yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
1246     myYeildingPasses.add(pass);
1247     myYeildingNow = true;
1248     yield(new Runnable() {
1249       public void run() {
1250         if (isReleased()) {
1251           return;
1252         }
1253
1254         runOnYieldingDone(new Runnable() {
1255           public void run() {
1256             if (isReleased()) {
1257               return;
1258             }
1259             executeYieldingRequest(runnable, pass);
1260           }
1261         });
1262       }
1263     });
1264   }
1265
1266   public boolean isYeildingNow() {
1267     return myYeildingNow;
1268   }
1269
1270   private boolean hasSheduledUpdates() {
1271     return getUpdater().hasNodesToUpdate() || isLoadingInBackgroundNow();
1272   }
1273
1274   public boolean isReady() {
1275     return isIdle() && !hasPendingWork() && !isNodeActionsPending();
1276   }
1277
1278   public boolean hasPendingWork() {
1279     return hasNodesToUpdate() || (myUpdaterState != null && myUpdaterState.isProcessingNow());
1280   }
1281
1282   public boolean isIdle() {
1283     return !isYeildingNow() && !isWorkerBusy() && (!hasSheduledUpdates() || getUpdater().isInPostponeMode());
1284   }
1285
1286   private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
1287     try {
1288       myYeildingPasses.remove(pass);
1289       runnable.run();
1290     }
1291     finally {
1292       maybeYeildingFinished();
1293     }
1294   }
1295
1296   private void maybeYeildingFinished() {
1297     if (myYeildingPasses.size() == 0) {
1298       myYeildingNow = false;
1299       flushPendingNodeActions();
1300     }
1301   }
1302
1303   void maybeReady() {
1304     if (isReleased()) return;
1305
1306     if (isReady()) {
1307       if (myTree.isShowing() || myUpdateIfInactive) {
1308         myInitialized.setDone();
1309       }
1310
1311       if (myTree.isShowing()) {
1312         if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
1313           TreeUtil.ensureSelection(myTree);
1314         }
1315       }
1316
1317       if (myInitialized.isDone()) {
1318         for (ActionCallback each : getReadyCallbacks(true)) {
1319           each.setDone();
1320         }
1321       }
1322     }
1323   }
1324
1325   private void flushPendingNodeActions() {
1326     final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
1327     myPendingNodeActions.clear();
1328
1329     for (DefaultMutableTreeNode each : nodes) {
1330       processNodeActionsIfReady(each);
1331     }
1332
1333     final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
1334     for (Runnable each : actions) {
1335       if (!isYeildingNow()) {
1336         myYeildingDoneRunnables.remove(each);
1337         each.run();
1338       }
1339     }
1340
1341     maybeReady();
1342   }
1343
1344   protected void runOnYieldingDone(Runnable onDone) {
1345     getBuilder().runOnYeildingDone(onDone);
1346   }
1347
1348   protected void yield(Runnable runnable) {
1349     getBuilder().yield(runnable);
1350   }
1351
1352   private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
1353     if (!canYield()) return false;
1354     return getBuilder().isToYieldUpdateFor(node);
1355   }
1356
1357   private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor,
1358                                                                @Nullable LoadedChildren preloadedChildren) {
1359     MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
1360     List children = preloadedChildren != null
1361                     ? preloadedChildren.getElements()
1362                     : Arrays.asList(getChildrenFor(getBuilder().getTreeStructureElement(descriptor)));
1363     int index = 0;
1364     for (Object child : children) {
1365       if (!isValid(child)) continue;
1366       elementToIndexMap.put(child, Integer.valueOf(index));
1367       index++;
1368     }
1369     return elementToIndexMap;
1370   }
1371
1372   private void expand(final DefaultMutableTreeNode node,
1373                       final NodeDescriptor descriptor,
1374                       final boolean wasLeaf,
1375                       final boolean canSmartExpand) {
1376     final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1377     alarm.addRequest(new Runnable() {
1378       public void run() {
1379         myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1380       }
1381     }, WAIT_CURSOR_DELAY);
1382
1383     if (wasLeaf && isAutoExpand(descriptor)) {
1384       expand(node, canSmartExpand);
1385     }
1386
1387     ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1388     for (TreeNode node1 : nodes) {
1389       final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1390       if (isLoadingNode(childNode)) continue;
1391       NodeDescriptor childDescr = getDescriptorFrom(childNode);
1392       if (isAutoExpand(childDescr)) {
1393         addNodeAction(getElementFor(childNode), new NodeAction() {
1394           public void onReady(DefaultMutableTreeNode node) {
1395             expand(childNode, canSmartExpand);
1396           }
1397         }, false);
1398         addSubtreeToUpdate(childNode);
1399       }
1400     }
1401
1402     int n = alarm.cancelAllRequests();
1403     if (n == 0) {
1404       myTree.setCursor(Cursor.getDefaultCursor());
1405     }
1406   }
1407
1408   public static boolean isLoadingNode(final Object node) {
1409     return node instanceof LoadingNode;
1410   }
1411
1412   private AsyncResult<ArrayList<TreeNode>> collectNodesToInsert(final NodeDescriptor descriptor,
1413                                                                 final MutualMap<Object, Integer> elementToIndexMap,
1414                                                                 final DefaultMutableTreeNode parent,
1415                                                                 final boolean addLoadingNode,
1416                                                                 @NotNull final LoadedChildren loadedChildren) {
1417     final AsyncResult<ArrayList<TreeNode>> result = new AsyncResult<ArrayList<TreeNode>>();
1418
1419     final ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1420     final Collection<Object> allElements = elementToIndexMap.getKeys();
1421
1422     final ActionCallback processingDone = new ActionCallback(allElements.size());
1423
1424     for (final Object child : allElements) {
1425       Integer index = elementToIndexMap.getValue(child);
1426       final Ref<NodeDescriptor> childDescr = new Ref<NodeDescriptor>(loadedChildren.getDescriptor(child));
1427       boolean needToUpdate = false;
1428       if (childDescr.get() == null) {
1429         childDescr.set(getTreeStructure().createDescriptor(child, descriptor));
1430         needToUpdate = true;
1431       }
1432
1433       //noinspection ConstantConditions
1434       if (childDescr.get() == null) {
1435         LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1436         processingDone.setDone();
1437         continue;
1438       }
1439       childDescr.get().setIndex(index.intValue());
1440
1441       final ActionCallback update = new ActionCallback();
1442       if (needToUpdate) {
1443         update(childDescr.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1444           public void run(Boolean changes) {
1445             loadedChildren.putDescriptor(child, childDescr.get(), changes);
1446             update.setDone();
1447           }
1448         });
1449       }
1450       else {
1451         update.setDone();
1452       }
1453
1454       update.doWhenDone(new Runnable() {
1455         public void run() {
1456           Object element = getElementFromDescriptor(childDescr.get());
1457           if (element == null) {
1458             processingDone.setDone();
1459             LOG.error("childDescr.getElement() == null, child = " + child + ", builder = " + this);
1460           }
1461           else {
1462             DefaultMutableTreeNode node = getNodeForElement(element, false);
1463             if (node == null || node.getParent() != parent) {
1464               final DefaultMutableTreeNode childNode = createChildNode(childDescr.get());
1465               if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr.get())) {
1466                 insertLoadingNode(childNode, true);
1467               }
1468               else {
1469                 addToUnbuilt(childNode);
1470               }
1471               nodesToInsert.add(childNode);
1472               createMapping(element, childNode);
1473             }
1474             processingDone.setDone();
1475           }
1476         }
1477       });
1478     }
1479
1480     processingDone.doWhenDone(new Runnable() {
1481       public void run() {
1482         result.setDone(nodesToInsert);
1483       }
1484     });
1485
1486     return result;
1487   }
1488
1489   protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1490     return new ElementNode(this, descriptor);
1491   }
1492
1493   protected boolean canYield() {
1494     return myCanYield && myYeildingUpdate.asBoolean();
1495   }
1496
1497   public long getClearOnHideDelay() {
1498     return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
1499   }
1500
1501   public ActionCallback getInitialized() {
1502     return myInitialized;
1503   }
1504
1505   public ActionCallback getReady(Object requestor) {
1506     if (isReady()) {
1507       return new ActionCallback.Done();
1508     }
1509     else {
1510       return addReadyCallback(requestor);
1511     }
1512   }
1513
1514   private void addToUpdating(DefaultMutableTreeNode node) {
1515     synchronized (myUpdatingChildren) {
1516       myUpdatingChildren.add(node);
1517     }
1518   }
1519
1520   private void removeFromUpdating(DefaultMutableTreeNode node) {
1521     synchronized (myUpdatingChildren) {
1522       myUpdatingChildren.remove(node);
1523     }
1524   }
1525
1526   public boolean isUpdatingNow(DefaultMutableTreeNode node) {
1527     synchronized (myUpdatingChildren) {
1528       return myUpdatingChildren.contains(node);
1529     }
1530   }
1531
1532   boolean hasUpdatingNow() {
1533     synchronized (myUpdatingChildren) {
1534       return myUpdatingChildren.size() > 0;
1535     }
1536   }
1537
1538   public Map getNodeActions() {
1539     return myNodeActions;
1540   }
1541
1542   public List<Object> getLoadedChildrenFor(Object element) {
1543     List<Object> result = new ArrayList<Object>();
1544
1545     DefaultMutableTreeNode node = (DefaultMutableTreeNode)getNodeForElement(element, false);
1546     if (node != null) {
1547       for (int i = 0; i < node.getChildCount(); i++) {
1548         TreeNode each = node.getChildAt(i);
1549         if (isLoadingNode(each)) continue;
1550
1551         result.add(getElementFor(each));
1552       }
1553     }
1554
1555     return result;
1556   }
1557
1558   public boolean hasNodesToUpdate() {
1559     return getUpdater().hasNodesToUpdate() || hasUpdatingNow() || isLoadingInBackgroundNow();
1560   }
1561
1562   public List<Object> getExpandedElements() {
1563     List<Object> result = new ArrayList<Object>();
1564     Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode()));
1565     while (enumeration.hasMoreElements()) {
1566       TreePath each = enumeration.nextElement();
1567       Object eachElement = getElementFor(each.getLastPathComponent());
1568       if (eachElement != null) {
1569         result.add(eachElement);
1570       }
1571     }
1572
1573     return result;
1574   }
1575
1576   static class ElementNode extends DefaultMutableTreeNode {
1577
1578     Set<Object> myElements = new HashSet<Object>();
1579     AbstractTreeUi myUi;
1580
1581     ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1582       super(descriptor);
1583       myUi = ui;
1584     }
1585
1586     @Override
1587     public void insert(final MutableTreeNode newChild, final int childIndex) {
1588       super.insert(newChild, childIndex);
1589       final Object element = myUi.getElementFor(newChild);
1590       if (element != null) {
1591         myElements.add(element);
1592       }
1593     }
1594
1595     @Override
1596     public void remove(final int childIndex) {
1597       final TreeNode node = getChildAt(childIndex);
1598       super.remove(childIndex);
1599       final Object element = myUi.getElementFor(node);
1600       if (element != null) {
1601         myElements.remove(element);
1602       }
1603     }
1604
1605     boolean isValidChild(Object childElement) {
1606       return myElements.contains(childElement);
1607     }
1608
1609     @Override
1610     public String toString() {
1611       return String.valueOf(getUserObject());
1612     }
1613   }
1614
1615   private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1616     return getUpdatingParent(kid) != null;
1617   }
1618
1619   private DefaultMutableTreeNode getUpdatingParent(DefaultMutableTreeNode kid) {
1620     DefaultMutableTreeNode eachParent = kid;
1621     while (eachParent != null) {
1622       if (isUpdatingNow(eachParent)) return eachParent;
1623       eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1624     }
1625
1626     return null;
1627   }
1628
1629   private boolean isLoadedInBackground(Object element) {
1630     return getLoadedInBackground(element) != null;
1631   }
1632
1633   private UpdateInfo getLoadedInBackground(Object element) {
1634     synchronized (myLoadedInBackground) {
1635       return myLoadedInBackground.get(element);
1636     }
1637   }
1638
1639   private void addToLoadedInBackground(Object element, UpdateInfo info) {
1640     synchronized (myLoadedInBackground) {
1641       myLoadedInBackground.put(element, info);
1642     }
1643   }
1644
1645   private void removeFromLoadedInBackground(final Object element) {
1646     synchronized (myLoadedInBackground) {
1647       myLoadedInBackground.remove(element);
1648     }
1649   }
1650
1651   private boolean isLoadingInBackgroundNow() {
1652     synchronized (myLoadedInBackground) {
1653       return myLoadedInBackground.size() > 0;
1654     }
1655   }
1656
1657   private boolean queueBackgroundUpdate(final UpdateInfo updateInfo, final DefaultMutableTreeNode node) {
1658     assertIsDispatchThread();
1659
1660     final Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor());
1661
1662     UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor);
1663     if (loaded != null) {
1664       loaded.apply(updateInfo);
1665       return false;
1666     }
1667
1668     addToLoadedInBackground(oldElementFromDescriptor, updateInfo);
1669
1670     if (!isNodeBeingBuilt(node)) {
1671       LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1672       myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1673     }
1674
1675     final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
1676     final Ref<Object> elementFromDescriptor = new Ref<Object>();
1677     Runnable buildRunnable = new Runnable() {
1678       public void run() {
1679         if (isReleased()) {
1680           return;
1681         }
1682
1683         if (!updateInfo.isDescriptorIsUpToDate()) {
1684           update(updateInfo.getDescriptor(), true);
1685         }
1686
1687         Object element = getElementFromDescriptor(updateInfo.getDescriptor());
1688         if (element == null) {
1689           removeFromLoadedInBackground(oldElementFromDescriptor);
1690           return;
1691         }
1692
1693         elementFromDescriptor.set(element);
1694
1695         Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(updateInfo.getDescriptor()));
1696         LoadedChildren loaded = new LoadedChildren(loadedElements);
1697         for (Object each : loadedElements) {
1698           NodeDescriptor eachChildDescriptor = getTreeStructure().createDescriptor(each, updateInfo.getDescriptor());
1699           loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).getResult());
1700         }
1701
1702         children.set(loaded);
1703       }
1704     };
1705
1706     final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
1707     Runnable updateRunnable = new Runnable() {
1708       public void run() {
1709         if (isReleased()) return;
1710         if (children.get() == null) return;
1711
1712         if (isRerunNeeded(updateInfo.getPass())) {
1713           removeFromLoadedInBackground(elementFromDescriptor.get());
1714           getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1715           return;
1716         }
1717
1718         removeFromLoadedInBackground(elementFromDescriptor.get());
1719
1720         if (myUnbuiltNodes.contains(node)) {
1721           Pair<Boolean, LoadedChildren> unbuilt =
1722             processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()),
1723                            children.get());
1724           if (unbuilt.getFirst()) {
1725             nodeToProcessActions[0] = node;
1726             return;
1727           }
1728         }
1729
1730         updateNodeChildren(node, updateInfo.getPass(), children.get(), true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(),
1731                            true);
1732
1733
1734         if (isRerunNeeded(updateInfo.getPass())) {
1735           getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1736           return;
1737         }
1738
1739         Object element = elementFromDescriptor.get();
1740
1741         if (element != null) {
1742           removeLoading(node, true);
1743           nodeToProcessActions[0] = node;
1744         }
1745       }
1746     };
1747     queueToBackground(buildRunnable, updateRunnable, new Runnable() {
1748       public void run() {
1749         if (nodeToProcessActions[0] != null) {
1750           processNodeActionsIfReady(nodeToProcessActions[0]);
1751         }
1752       }
1753     });
1754     return true;
1755   }
1756
1757   private boolean isExpanded(DefaultMutableTreeNode node, boolean isExpanded) {
1758     return isExpanded || myTree.isExpanded(getPathFor(node));
1759   }
1760
1761   private void removeLoading(DefaultMutableTreeNode parent, boolean removeFromUnbuilt) {
1762     for (int i = 0; i < parent.getChildCount(); i++) {
1763       TreeNode child = parent.getChildAt(i);
1764       if (removeIfLoading(child)) {
1765         i--;
1766       }
1767     }
1768
1769     if (removeFromUnbuilt) {
1770       removeFromUnbuilt(parent);
1771     }
1772
1773     if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
1774       insertLoadingNode(parent, false);
1775     }
1776
1777     maybeReady();
1778   }
1779
1780   private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1781     assertIsDispatchThread();
1782
1783     if (isNodeBeingBuilt(node)) return;
1784
1785     final Object o = node.getUserObject();
1786     if (!(o instanceof NodeDescriptor)) return;
1787
1788
1789     if (isYeildingNow()) {
1790       myPendingNodeActions.add(node);
1791       return;
1792     }
1793
1794     final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1795
1796     boolean childrenReady = !isLoadedInBackground(element);
1797
1798     processActions(node, element, myNodeActions, childrenReady ? myNodeChildrenActions : null);
1799     if (childrenReady) {
1800       processActions(node, element, myNodeChildrenActions, null);
1801     }
1802
1803     if (!isUpdatingParent(node) && !isWorkerBusy()) {
1804       final UpdaterTreeState state = myUpdaterState;
1805       if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1806         if (!state.restore(childrenReady ? node : null)) {
1807           setUpdaterState(state);
1808         }
1809       }
1810     }
1811
1812     maybeReady();
1813   }
1814
1815
1816   private void processActions(DefaultMutableTreeNode node, Object element, final Map<Object, List<NodeAction>> nodeActions, @Nullable final Map<Object, List<NodeAction>> secondaryNodeAction) {
1817     final List<NodeAction> actions = nodeActions.get(element);
1818     if (actions != null) {
1819       nodeActions.remove(element);
1820
1821       List<NodeAction> secondary = secondaryNodeAction != null ? secondaryNodeAction.get(element) : null;
1822       for (NodeAction each : actions) {
1823         if (secondary != null && secondary.contains(each)) {
1824           secondary.remove(each);          
1825         }
1826         each.onReady(node);
1827       }
1828     }
1829   }
1830
1831
1832   private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1833     return !myNotForSmartExpand.contains(node) && canSmartExpand;
1834   }
1835
1836   private void processSmartExpand(final DefaultMutableTreeNode node, final boolean canSmartExpand) {
1837     if (!getBuilder().isSmartExpand() || !canSmartExpand(node, canSmartExpand)) return;
1838
1839     if (isNodeBeingBuilt(node)) {
1840       addNodeAction(getElementFor(node), new NodeAction() {
1841         public void onReady(DefaultMutableTreeNode node) {
1842           processSmartExpand(node, canSmartExpand);
1843         }
1844       }, true);
1845     }
1846     else {
1847       TreeNode child = getChildForSmartExpand(node);
1848       if (child != null) {
1849         final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child);
1850         myTree.expandPath(childPath);
1851       }
1852     }
1853   }
1854
1855   @Nullable
1856   private TreeNode getChildForSmartExpand(DefaultMutableTreeNode node) {
1857     int realChildCount = 0;
1858     TreeNode nodeToExpand = null;
1859
1860     for (int i = 0; i < node.getChildCount(); i++) {
1861       TreeNode eachChild = node.getChildAt(i);
1862
1863       if (!isLoadingNode(eachChild)) {
1864         realChildCount++;
1865         if (nodeToExpand == null) {
1866           nodeToExpand = eachChild;
1867         }
1868       }
1869
1870       if (realChildCount > 1) {
1871         nodeToExpand = null;
1872         break;
1873       }
1874     }
1875
1876     return nodeToExpand;
1877   }
1878
1879   public boolean isLoadingChildrenFor(final Object nodeObject) {
1880     if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1881
1882     DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1883
1884     int loadingNodes = 0;
1885     for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
1886       TreeNode child = node.getChildAt(i);
1887       if (isLoadingNode(child)) {
1888         loadingNodes++;
1889       }
1890     }
1891     return loadingNodes > 0 && loadingNodes == node.getChildCount();
1892   }
1893
1894   private boolean isParentLoading(Object nodeObject) {
1895     return getParentLoading(nodeObject) != null;
1896   }
1897
1898   private DefaultMutableTreeNode getParentLoading(Object nodeObject) {
1899     if (!(nodeObject instanceof DefaultMutableTreeNode)) return null;
1900
1901     DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1902
1903     TreeNode eachParent = node.getParent();
1904
1905     while (eachParent != null) {
1906       eachParent = eachParent.getParent();
1907       if (eachParent instanceof DefaultMutableTreeNode) {
1908         final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
1909         if (isLoadedInBackground(eachElement)) return (DefaultMutableTreeNode)eachParent;
1910       }
1911     }
1912
1913     return null;
1914   }
1915
1916   protected String getLoadingNodeText() {
1917     return IdeBundle.message("progress.searching");
1918   }
1919
1920   private ActionCallback processExistingNode(final DefaultMutableTreeNode childNode,
1921                                              final NodeDescriptor childDescriptor,
1922                                              final DefaultMutableTreeNode parentNode,
1923                                              final MutualMap<Object, Integer> elementToIndexMap,
1924                                              final TreeUpdatePass pass,
1925                                              final boolean canSmartExpand,
1926                                              final boolean forceUpdate,
1927                                              LoadedChildren parentPreloadedChildren) {
1928
1929     final ActionCallback result = new ActionCallback();
1930
1931     if (pass.isExpired()) {
1932       return new ActionCallback.Rejected();
1933     }
1934
1935     final Ref<NodeDescriptor> childDesc = new Ref<NodeDescriptor>(childDescriptor);
1936
1937     if (childDesc.get() == null) {
1938       pass.expire();
1939       return new ActionCallback.Rejected();
1940     }
1941     final Object oldElement = getElementFromDescriptor(childDesc.get());
1942     if (oldElement == null) {
1943       pass.expire();
1944       return new ActionCallback.Rejected();
1945     }
1946
1947     AsyncResult<Boolean> update = new AsyncResult<Boolean>();
1948     if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) {
1949       update.setDone(parentPreloadedChildren.isUpdated(oldElement));
1950     }
1951     else {
1952       update = update(childDesc.get(), false);
1953     }
1954
1955     update.doWhenDone(new AsyncResult.Handler<Boolean>() {
1956       public void run(Boolean isChanged) {
1957         final Ref<Boolean> changes = new Ref<Boolean>(isChanged);
1958
1959         final Ref<Boolean> forceRemapping = new Ref<Boolean>(false);
1960         final Ref<Object> newElement = new Ref<Object>(getElementFromDescriptor(childDesc.get()));
1961
1962         final Integer index = newElement.get() != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc.get())) : null;
1963         final AsyncResult<Boolean> updateIndexDone = new AsyncResult<Boolean>();
1964         final ActionCallback indexReady = new ActionCallback();
1965         if (index != null) {
1966           final Object elementFromMap = elementToIndexMap.getKey(index);
1967           if (elementFromMap != newElement.get() && elementFromMap.equals(newElement.get())) {
1968             if (isInStructure(elementFromMap) && isInStructure(newElement.get())) {
1969               if (parentNode.getUserObject() instanceof NodeDescriptor) {
1970                 final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
1971                 childDesc.set(getTreeStructure().createDescriptor(elementFromMap, parentDescriptor));
1972                 childNode.setUserObject(childDesc.get());
1973                 newElement.set(elementFromMap);
1974                 forceRemapping.set(true);
1975                 update(childDesc.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1976                   public void run(Boolean isChanged) {
1977                     changes.set(isChanged);
1978                     updateIndexDone.setDone(isChanged);
1979                   }
1980                 });
1981               }
1982             }
1983             else {
1984               updateIndexDone.setDone(changes.get());
1985             }
1986           } else {
1987             updateIndexDone.setDone(changes.get());
1988           }
1989
1990           updateIndexDone.doWhenDone(new Runnable() {
1991             public void run() {
1992               if (childDesc.get().getIndex() != index.intValue()) {
1993                 changes.set(true);
1994               }
1995               childDesc.get().setIndex(index.intValue());
1996               indexReady.setDone();
1997             }
1998           });
1999         }
2000         else {
2001           updateIndexDone.setDone();
2002         }
2003
2004         updateIndexDone.doWhenDone(new Runnable() {
2005           public void run() {
2006             if (index != null && changes.get()) {
2007               updateNodeImageAndPosition(childNode, false);
2008             }
2009             if (!oldElement.equals(newElement.get()) | forceRemapping.get()) {
2010               removeMapping(oldElement, childNode, newElement.get());
2011               if (newElement.get() != null) {
2012                 createMapping(newElement.get(), childNode);
2013               }
2014               getDescriptorFrom(parentNode).setChildrenSortingStamp(-1);
2015             }
2016
2017             if (index == null) {
2018               int selectedIndex = -1;
2019               if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
2020                 selectedIndex = parentNode.getIndex(childNode);
2021               }
2022
2023               if (childNode.getParent() instanceof DefaultMutableTreeNode) {
2024                 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
2025                 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
2026                   if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
2027                     insertLoadingNode(parent, false);
2028                   }
2029                 }
2030               }
2031
2032               Object disposedElement = getElementFor(childNode);
2033
2034               removeNodeFromParent(childNode, selectedIndex >= 0);
2035               disposeNode(childNode);
2036
2037               adjustSelectionOnChildRemove(parentNode, selectedIndex, disposedElement);
2038             }
2039             else {
2040               elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc.get()));
2041               updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
2042             }
2043
2044             if (parentNode.equals(getRootNode())) {
2045               myTreeModel.nodeChanged(getRootNode());
2046             }
2047
2048             result.setDone();
2049           }
2050         });
2051       }
2052     });
2053
2054
2055     return result;
2056   }
2057
2058   private void adjustSelectionOnChildRemove(DefaultMutableTreeNode parentNode, int selectedIndex, Object disposedElement) {
2059     DefaultMutableTreeNode node = getNodeForElement(disposedElement, false);
2060     if (node != null && isValidForSelectionAdjusting(node)) {
2061       Object newElement = getElementFor(node);
2062       addSelectionPath(getPathFor(node), true, getExpiredElementCondition(newElement));
2063       return;
2064     }
2065
2066
2067     if (selectedIndex >= 0) {
2068       if (parentNode.getChildCount() > 0) {
2069         if (parentNode.getChildCount() > selectedIndex) {
2070           TreeNode newChildNode = parentNode.getChildAt(selectedIndex);
2071           if (isValidForSelectionAdjusting(newChildNode)) {
2072             addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement));
2073           }
2074         }
2075         else {
2076           TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1);
2077           if (isValidForSelectionAdjusting(newChild)) {
2078             addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement));
2079           }
2080         }
2081       }
2082       else {
2083         addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement));
2084       }
2085     }
2086   }
2087
2088   private boolean isValidForSelectionAdjusting(TreeNode node) {
2089     if (!myTree.isRootVisible() && getRootNode() == node) return false;
2090
2091     if (isLoadingNode(node)) return true;
2092
2093     final Object elementInTree = getElementFor(node);
2094     if (elementInTree == null) return false;
2095
2096     final TreeNode parentNode = node.getParent();
2097     final Object parentElementInTree = getElementFor(parentNode);
2098     if (parentElementInTree == null) return false;
2099
2100     final Object parentElement = getTreeStructure().getParentElement(elementInTree);
2101
2102     return parentElementInTree.equals(parentElement);
2103   }
2104
2105   public Condition getExpiredElementCondition(final Object element) {
2106     return new Condition() {
2107       public boolean value(final Object o) {
2108         return isInStructure(element);
2109       }
2110     };
2111   }
2112
2113   private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement) {
2114     doWithUpdaterState(new Runnable() {
2115       public void run() {
2116         TreePath toSelect = null;
2117
2118         if (isLoadingNode(path.getLastPathComponent())) {
2119           final TreePath parentPath = path.getParentPath();
2120           if (parentPath != null) {
2121             if (isValidForSelectionAdjusting((TreeNode)parentPath.getLastPathComponent())) {
2122               toSelect = parentPath;
2123             }
2124             else {
2125               toSelect = null;
2126             }
2127           }
2128         }
2129         else {
2130           toSelect = path;
2131         }
2132
2133         if (toSelect != null) {
2134           myTree.addSelectionPath(toSelect);
2135
2136           if (isAdjustedSelection && myUpdaterState != null) {
2137             final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
2138             myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement);
2139           }
2140         }
2141       }
2142     });
2143   }
2144
2145   private static TreePath getPathFor(TreeNode node) {
2146     if (node instanceof DefaultMutableTreeNode) {
2147       return new TreePath(((DefaultMutableTreeNode)node).getPath());
2148     }
2149     else {
2150       ArrayList nodes = new ArrayList();
2151       TreeNode eachParent = node;
2152       while (eachParent != null) {
2153         nodes.add(eachParent);
2154         eachParent = eachParent.getParent();
2155       }
2156
2157       return new TreePath(ArrayUtil.toObjectArray(nodes));
2158     }
2159   }
2160
2161
2162   private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
2163     doWithUpdaterState(new Runnable() {
2164       public void run() {
2165         if (willAdjustSelection) {
2166           final TreePath path = getPathFor(node);
2167           if (myTree.isPathSelected(path)) {
2168             myTree.removeSelectionPath(path);
2169           }
2170         }
2171
2172         myTreeModel.removeNodeFromParent(node);
2173       }
2174     });
2175   }
2176
2177   private void expandPath(final TreePath path, final boolean canSmartExpand) {
2178     doWithUpdaterState(new Runnable() {
2179       public void run() {
2180         if (path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2181           DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2182           if (node.getChildCount() > 0 && !myTree.isExpanded(path)) {
2183             if (!canSmartExpand) {
2184               myNotForSmartExpand.add(node);
2185             }
2186             try {
2187               myRequestedExpand = path;
2188               myTree.expandPath(path);
2189               processSmartExpand(node, canSmartExpand);
2190             }
2191             finally {
2192               myNotForSmartExpand.remove(node);
2193               myRequestedExpand = null;
2194             }
2195           }
2196           else {
2197             processNodeActionsIfReady(node);
2198           }
2199         }
2200       }
2201     });
2202   }
2203
2204   private void doWithUpdaterState(Runnable runnable) {
2205     if (myUpdaterState != null) {
2206       myUpdaterState.process(runnable);
2207     }
2208     else {
2209       runnable.run();
2210     }
2211   }
2212
2213   protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
2214     return descriptor.update();
2215   }
2216
2217   private void makeLoadingOrLeafIfNoChildren(final DefaultMutableTreeNode node) {
2218     TreePath path = getPathFor(node);
2219     if (path == null) return;
2220
2221     insertLoadingNode(node, true);
2222
2223     final NodeDescriptor descriptor = getDescriptorFrom(node);
2224     if (descriptor == null) return;
2225
2226     descriptor.setChildrenSortingStamp(-1);
2227
2228     if (getBuilder().isAlwaysShowPlus(descriptor)) return;
2229
2230
2231     TreePath parentPath = path.getParentPath();
2232     if (myTree.isVisible(path) || (parentPath != null && myTree.isExpanded(parentPath))) {
2233       if (myTree.isExpanded(path)) {
2234         addSubtreeToUpdate(node);
2235       }
2236       else {
2237         insertLoadingNode(node, false);
2238       }
2239     }
2240   }
2241
2242
2243   private boolean isValid(DefaultMutableTreeNode node) {
2244     if (node == null) return false;
2245     final Object object = node.getUserObject();
2246     if (object instanceof NodeDescriptor) {
2247       return isValid((NodeDescriptor)object);
2248     }
2249
2250     return false;
2251   }
2252
2253   private boolean isValid(NodeDescriptor descriptor) {
2254     if (descriptor == null) return false;
2255     return isValid(getElementFromDescriptor(descriptor));
2256   }
2257
2258   private boolean isValid(Object element) {
2259     if (element instanceof ValidateableNode) {
2260       if (!((ValidateableNode)element).isValid()) return false;
2261     }
2262     return getBuilder().validateNode(element);
2263   }
2264
2265   private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
2266     if (!isLoadingChildrenFor(node)) {
2267       myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
2268     }
2269
2270     if (addToUnbuilt) {
2271       addToUnbuilt(node);
2272     }
2273   }
2274
2275
2276   protected void queueToBackground(@NotNull final Runnable bgBuildAction,
2277                                    @Nullable final Runnable edtPostRunnable,
2278                                    @Nullable final Runnable finalizeEdtRunnable) {
2279     registerWorkerTask(bgBuildAction);
2280
2281     final Runnable pooledThreadWithProgressRunnable = new Runnable() {
2282       public void run() {
2283         if (isReleased()) {
2284           return;
2285         }
2286
2287         final AbstractTreeBuilder builder = getBuilder();
2288
2289         builder.runBackgroundLoading(new Runnable() {
2290           public void run() {
2291             assertNotDispatchThread();
2292
2293             if (isReleased()) {
2294               return;
2295             }
2296
2297             try {
2298               bgBuildAction.run();
2299
2300               if (edtPostRunnable != null && !isReleased()) {
2301                 builder.updateAfterLoadedInBackground(new Runnable() {
2302                   public void run() {
2303                     try {
2304                       assertIsDispatchThread();
2305
2306                       if (isReleased()) {
2307                         return;
2308                       }
2309
2310                       edtPostRunnable.run();
2311                     }
2312                     finally {
2313                       unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2314                     }
2315                   }
2316                 });
2317               }
2318               else {
2319                 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2320               }
2321             }
2322             catch (ProcessCanceledException e) {
2323               unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2324             }
2325             catch (Throwable t) {
2326               unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2327               throw new RuntimeException(t);
2328             }
2329           }
2330         });
2331       }
2332     };
2333
2334     Runnable pooledThreadRunnable = new Runnable() {
2335       public void run() {
2336         if (isReleased()) return;
2337
2338         try {
2339           if (myProgress != null) {
2340             ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress);
2341           }
2342           else {
2343             pooledThreadWithProgressRunnable.run();
2344           }
2345         }
2346         catch (ProcessCanceledException e) {
2347           //ignore
2348         }
2349       }
2350     };
2351
2352     if (isPassthroughMode()) {
2353
2354     } else {
2355       if (myWorker == null || myWorker.isDisposed()) {
2356         myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
2357         myWorker.start();
2358         myWorker.addTaskFirst(pooledThreadRunnable);
2359         myWorker.dispose(false);
2360       }
2361       else {
2362         myWorker.addTaskFirst(pooledThreadRunnable);
2363       }
2364     }
2365   }
2366
2367   private void registerWorkerTask(Runnable runnable) {
2368     synchronized (myActiveWorkerTasks) {
2369       myActiveWorkerTasks.add(runnable);
2370     }
2371   }
2372
2373   private void unregisterWorkerTask(Runnable runnable, @Nullable Runnable finalizeRunnable) {
2374     boolean wasRemoved;
2375     synchronized (myActiveWorkerTasks) {
2376       wasRemoved = myActiveWorkerTasks.remove(runnable);
2377     }
2378
2379     if (wasRemoved && finalizeRunnable != null) {
2380       finalizeRunnable.run();
2381     }
2382
2383     maybeReady();
2384   }
2385
2386   public boolean isWorkerBusy() {
2387     synchronized (myActiveWorkerTasks) {
2388       return myActiveWorkerTasks.size() > 0;
2389     }
2390   }
2391
2392   private void clearWorkerTasks() {
2393     synchronized (myActiveWorkerTasks) {
2394       myActiveWorkerTasks.clear();
2395     }
2396   }
2397
2398   private void updateNodeImageAndPosition(final DefaultMutableTreeNode node, boolean updatePosition) {
2399     if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2400     NodeDescriptor descriptor = getDescriptorFrom(node);
2401     if (getElementFromDescriptor(descriptor) == null) return;
2402
2403     boolean notified = false;
2404     if (updatePosition) {
2405       DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
2406       if (parentNode != null) {
2407         int oldIndex = parentNode.getIndex(node);
2408         int newIndex = oldIndex;
2409         if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
2410           final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
2411           for (int i = 0; i < parentNode.getChildCount(); i++) {
2412             children.add(parentNode.getChildAt(i));
2413           }
2414           sortChildren(node, children, true, false);
2415           newIndex = children.indexOf(node);
2416         }
2417
2418         if (oldIndex != newIndex) {
2419           List<Object> pathsToExpand = new ArrayList<Object>();
2420           List<Object> selectionPaths = new ArrayList<Object>();
2421           TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
2422           removeNodeFromParent(node, false);
2423           myTreeModel.insertNodeInto(node, parentNode, newIndex);
2424           TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
2425           notified = true;
2426         }
2427         else {
2428           myTreeModel.nodeChanged(node);
2429           notified = true;
2430         }
2431       }
2432       else {
2433         myTreeModel.nodeChanged(node);
2434         notified = true;
2435       }
2436     }
2437
2438     if (!notified) {
2439       myTreeModel.nodeChanged(node);
2440     }
2441
2442   }
2443
2444   public DefaultTreeModel getTreeModel() {
2445     return myTreeModel;
2446   }
2447
2448   private void insertNodesInto(final ArrayList<TreeNode> toInsert, final DefaultMutableTreeNode parentNode) {
2449     sortChildren(parentNode, toInsert, false, true);
2450     final ArrayList<TreeNode> all = new ArrayList<TreeNode>(toInsert.size() + parentNode.getChildCount());
2451     all.addAll(toInsert);
2452     all.addAll(TreeUtil.childrenToArray(parentNode));
2453
2454     if (toInsert.size() > 0) {
2455       sortChildren(parentNode, all, true, true);
2456
2457       int[] newNodeIndices = new int[toInsert.size()];
2458       int eachNewNodeIndex = 0;
2459       TreeMap<Integer, TreeNode> insertSet = new TreeMap<Integer, TreeNode>();
2460       for (int i = 0; i < toInsert.size(); i++) {
2461         TreeNode eachNewNode = toInsert.get(i);
2462         while (all.get(eachNewNodeIndex) != eachNewNode) {
2463           eachNewNodeIndex++;
2464         }
2465         newNodeIndices[i] = eachNewNodeIndex;
2466         insertSet.put(eachNewNodeIndex, eachNewNode);
2467       }
2468
2469       Iterator<Integer> indices = insertSet.keySet().iterator();
2470       while (indices.hasNext()) {
2471         Integer eachIndex = indices.next();
2472         TreeNode eachNode = insertSet.get(eachIndex);
2473         parentNode.insert((MutableTreeNode)eachNode, eachIndex);
2474       }
2475
2476       myTreeModel.nodesWereInserted(parentNode, newNodeIndices);
2477     }
2478     else {
2479       ArrayList<TreeNode> before = new ArrayList<TreeNode>();
2480       before.addAll(all);
2481
2482       sortChildren(parentNode, all, true, false);
2483       if (!before.equals(all)) {
2484         doWithUpdaterState(new Runnable() {
2485           public void run() {
2486             parentNode.removeAllChildren();
2487             for (TreeNode each : all) {
2488               parentNode.add((MutableTreeNode)each);
2489             }
2490             myTreeModel.nodeStructureChanged(parentNode);
2491           }
2492         });
2493       }
2494     }
2495   }
2496
2497   private void sortChildren(DefaultMutableTreeNode node, ArrayList<TreeNode> children, boolean updateStamp, boolean forceSort) {
2498     NodeDescriptor descriptor = getDescriptorFrom(node);
2499     assert descriptor != null;
2500
2501     if (descriptor.getChildrenSortingStamp() >= getComparatorStamp() && !forceSort) return;
2502     if (children.size() > 0) {
2503       getBuilder().sortChildren(myNodeComparator, node, children);
2504     }
2505
2506     if (updateStamp) {
2507       descriptor.setChildrenSortingStamp(getComparatorStamp());
2508     }
2509   }
2510
2511   private void disposeNode(DefaultMutableTreeNode node) {
2512     TreeNode parent = node.getParent();
2513     if (parent instanceof DefaultMutableTreeNode) {
2514       addToUnbuilt((DefaultMutableTreeNode)parent);
2515     }
2516
2517     if (node.getChildCount() > 0) {
2518       for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
2519         disposeNode(_node);
2520       }
2521     }
2522
2523     removeFromUpdating(node);
2524     removeFromUnbuilt(node);
2525
2526     if (isLoadingNode(node)) return;
2527     NodeDescriptor descriptor = getDescriptorFrom(node);
2528     if (descriptor == null) return;
2529     final Object element = getElementFromDescriptor(descriptor);
2530     removeMapping(element, node, null);
2531     node.setUserObject(null);
2532     node.removeAllChildren();
2533   }
2534
2535   public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root) {
2536     return addSubtreeToUpdate(root, null);
2537   }
2538
2539   public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
2540     Object element = getElementFor(root);
2541     if (getTreeStructure().isAlwaysLeaf(element)) {
2542       removeLoading(root, true);
2543
2544       if (runAfterUpdate != null) {
2545         getReady(this).doWhenDone(runAfterUpdate);
2546       }
2547       return false;
2548     }
2549
2550     getUpdater().runAfterUpdate(runAfterUpdate);
2551     getUpdater().addSubtreeToUpdate(root);
2552
2553     return true;
2554   }
2555
2556   public boolean wasRootNodeInitialized() {
2557     return myRootNodeWasInitialized;
2558   }
2559
2560   private boolean isRootNodeBuilt() {
2561     return myRootNodeWasInitialized && isNodeBeingBuilt(myRootNode);
2562   }
2563
2564   public void select(final Object[] elements, @Nullable final Runnable onDone) {
2565     select(elements, onDone, false);
2566   }
2567
2568   public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
2569     select(elements, onDone, addToSelection, false);
2570   }
2571
2572   public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) {
2573     _select(elements, onDone, addToSelection, true, false, true, deferred, false, false);
2574   }
2575
2576   void _select(final Object[] elements,
2577                final Runnable onDone,
2578                final boolean addToSelection,
2579                final boolean checkCurrentSelection,
2580                final boolean checkIfInStructure) {
2581
2582     _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, true, false, false, false);
2583   }
2584
2585   void _select(final Object[] elements,
2586                final Runnable onDone,
2587                final boolean addToSelection,
2588                final boolean checkCurrentSelection,
2589                final boolean checkIfInStructure,
2590                final boolean scrollToVisible) {
2591
2592     _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, false, false, false);
2593   }
2594
2595   public void userSelect(final Object[] elements,
2596                final Runnable onDone,
2597                final boolean addToSelection,
2598                boolean scroll) {
2599     _select(elements, onDone, addToSelection, true, false, scroll, false, true, true);    
2600   }
2601
2602   void _select(final Object[] elements,
2603                final Runnable onDone,
2604                final boolean addToSelection,
2605                final boolean checkCurrentSelection,
2606                final boolean checkIfInStructure,
2607                final boolean scrollToVisible,
2608                final boolean deferred,
2609                final boolean canSmartExpand,
2610                final boolean mayQueue) {
2611
2612     AbstractTreeUpdater updater = getUpdater();
2613     if (mayQueue && updater != null) {
2614       updater.queueSelection(new SelectionRequest(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, deferred, canSmartExpand));
2615       return;
2616     }
2617
2618     boolean willAffectSelection = elements.length > 0 || (elements.length == 0 && addToSelection);
2619     if (!willAffectSelection) {
2620       runDone(onDone);
2621       return;
2622     }
2623
2624     final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections;
2625
2626     if (!deferred && wasRootNodeInitialized() && willAffectSelection) {
2627       myCanProcessDeferredSelections = false;
2628     }
2629
2630     if (!checkDeferred(deferred, onDone)) return;
2631
2632     if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) {
2633       getTree().clearSelection();
2634     }
2635
2636
2637     runDone(new Runnable() {
2638       public void run() {
2639         if (!checkDeferred(deferred, onDone)) return;
2640
2641         final Set<Object> currentElements = getSelectedElements();
2642
2643         if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
2644           boolean runSelection = false;
2645           for (Object eachToSelect : elements) {
2646             if (!currentElements.contains(eachToSelect)) {
2647               runSelection = true;
2648               break;
2649             }
2650           }
2651
2652           if (!runSelection) {
2653             if (elements.length > 0) {
2654               selectVisible(elements[0], onDone, true, true, scrollToVisible);
2655             }
2656             return;
2657           }
2658         }
2659
2660         Set<Object> toSelect = new HashSet<Object>();
2661         myTree.clearSelection();
2662         toSelect.addAll(Arrays.asList(elements));
2663         if (addToSelection) {
2664           toSelect.addAll(currentElements);
2665         }
2666
2667         if (checkIfInStructure) {
2668           final Iterator<Object> allToSelect = toSelect.iterator();
2669           while (allToSelect.hasNext()) {
2670             Object each = allToSelect.next();
2671             if (!isInStructure(each)) {
2672               allToSelect.remove();
2673             }
2674           }
2675         }
2676
2677         final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
2678
2679         if (wasRootNodeInitialized()) {
2680           final int[] originalRows = myTree.getSelectionRows();
2681           if (!addToSelection) {
2682             myTree.clearSelection();
2683           }
2684           addNext(elementsToSelect, 0, new Runnable() {
2685             public void run() {
2686               if (getTree().isSelectionEmpty()) {
2687                 restoreSelection(currentElements);
2688               }
2689               runDone(onDone);
2690             }
2691           }, originalRows, deferred, scrollToVisible, canSmartExpand);
2692         }
2693         else {
2694           addToDeferred(elementsToSelect, onDone);
2695         }
2696       }
2697     });
2698   }
2699
2700   private void restoreSelection(Set<Object> selection) {
2701     for (Object each : selection) {
2702       DefaultMutableTreeNode node = getNodeForElement(each, false);
2703       if (node != null && isValidForSelectionAdjusting(node)) {
2704         addSelectionPath(getPathFor(node), false, null);
2705       }
2706     }
2707   }
2708
2709
2710   private void addToDeferred(final Object[] elementsToSelect, final Runnable onDone) {
2711     myDeferredSelections.clear();
2712     myDeferredSelections.add(new Runnable() {
2713       public void run() {
2714         select(elementsToSelect, onDone, false, true);
2715       }
2716     });
2717   }
2718
2719   private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) {
2720     if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) {
2721       return true;
2722     }
2723     else {
2724       runDone(onDone);
2725       return false;
2726     }
2727   }
2728
2729   @NotNull
2730   final Set<Object> getSelectedElements() {
2731     final TreePath[] paths = myTree.getSelectionPaths();
2732
2733     Set<Object> result = new HashSet<Object>();
2734     if (paths != null) {
2735       for (TreePath eachPath : paths) {
2736         if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2737           final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
2738           final Object eachElement = getElementFor(eachNode);
2739           if (eachElement != null) {
2740             result.add(eachElement);
2741           }
2742         }
2743       }
2744     }
2745     return result;
2746   }
2747
2748
2749   private void addNext(final Object[] elements,
2750                        final int i,
2751                        @Nullable final Runnable onDone,
2752                        final int[] originalRows,
2753                        final boolean deferred,
2754                        final boolean scrollToVisible,
2755                        final boolean canSmartExpand) {
2756     if (i >= elements.length) {
2757       if (myTree.isSelectionEmpty()) {
2758         myTree.setSelectionRows(originalRows);
2759       }
2760       runDone(onDone);
2761     }
2762     else {
2763       if (!checkDeferred(deferred, onDone)) {
2764         return;
2765       }
2766
2767       doSelect(elements[i], new Runnable() {
2768         public void run() {
2769           if (!checkDeferred(deferred, onDone)) return;
2770
2771           addNext(elements, i + 1, onDone, originalRows, deferred, scrollToVisible, canSmartExpand);
2772         }
2773       }, true, deferred, i == 0, scrollToVisible, canSmartExpand);
2774     }
2775   }
2776
2777   public void select(final Object element, @Nullable final Runnable onDone) {
2778     select(element, onDone, false);
2779   }
2780
2781   public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
2782     _select(new Object[]{element}, onDone, addToSelection, true, false);
2783   }
2784
2785   private void doSelect(final Object element,
2786                         final Runnable onDone,
2787                         final boolean addToSelection,
2788                         final boolean deferred,
2789                         final boolean canBeCentered,
2790                         final boolean scrollToVisible,
2791                         boolean canSmartExpand) {
2792     final Runnable _onDone = new Runnable() {
2793       public void run() {
2794         if (!checkDeferred(deferred, onDone)) return;
2795         selectVisible(element, onDone, addToSelection, canBeCentered, scrollToVisible);
2796       }
2797     };
2798     _expand(element, _onDone, true, false, canSmartExpand);
2799   }
2800
2801   public void scrollSelectionToVisible(@Nullable Runnable onDone, boolean shouldBeCentered) {
2802     int[] rows = myTree.getSelectionRows();
2803     if (rows == null || rows.length == 0) {
2804       runDone(onDone);
2805       return;
2806     }
2807
2808
2809     Object toSelect = null;
2810     for (int eachRow : rows) {
2811       TreePath path = myTree.getPathForRow(eachRow);
2812       toSelect = getElementFor(path.getLastPathComponent());
2813       if (toSelect != null) break;
2814     }
2815
2816     if (toSelect != null) {
2817       selectVisible(toSelect, onDone, true, shouldBeCentered, true);
2818     }
2819   }
2820
2821   private void selectVisible(Object element, final Runnable onDone, boolean addToSelection, boolean canBeCentered, final boolean scroll) {
2822     final DefaultMutableTreeNode toSelect = getNodeForElement(element, false);
2823
2824     if (toSelect == null) {
2825       runDone(onDone);
2826       return;
2827     }
2828
2829     if (getRootNode() == toSelect && !myTree.isRootVisible()) {
2830       runDone(onDone);
2831       return;
2832     }
2833
2834     final int row = myTree.getRowForPath(new TreePath(toSelect.getPath()));
2835
2836     if (myUpdaterState != null) {
2837       myUpdaterState.addSelection(element);
2838     }
2839
2840     if (Registry.is("ide.tree.autoscrollToVCenter") && canBeCentered) {
2841       runDone(new Runnable() {
2842         public void run() {
2843           TreeUtil.showRowCentered(myTree, row, false, scroll).doWhenDone(new Runnable() {
2844             public void run() {
2845               runDone(onDone);
2846             }
2847           });
2848         }
2849       });
2850     }
2851     else {
2852       TreeUtil.showAndSelect(myTree, row - 2, row + 2, row, -1, addToSelection, scroll).doWhenDone(new Runnable() {
2853         public void run() {
2854           runDone(onDone);
2855         }
2856       });
2857     }
2858   }
2859
2860   public void expand(final Object element, @Nullable final Runnable onDone) {
2861     expand(new Object[]{element}, onDone);
2862   }
2863