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