TreeUi: initial selection (on maybeReady handler)
[fedora-idea.git] / platform / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUi.java
blob72762ead7fbe0a2f825c2ae4c45a9db49bd546ed
1 package com.intellij.ide.util.treeView;
3 import com.intellij.ide.IdeBundle;
4 import com.intellij.openapi.application.Application;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.diagnostic.Logger;
7 import com.intellij.openapi.progress.ProcessCanceledException;
8 import com.intellij.openapi.progress.ProgressIndicator;
9 import com.intellij.openapi.progress.ProgressManager;
10 import com.intellij.openapi.project.IndexNotReadyException;
11 import com.intellij.openapi.util.*;
12 import com.intellij.openapi.util.registry.Registry;
13 import com.intellij.openapi.util.registry.RegistryValue;
14 import com.intellij.ui.LoadingNode;
15 import com.intellij.ui.treeStructure.Tree;
16 import com.intellij.util.Alarm;
17 import com.intellij.util.ArrayUtil;
18 import com.intellij.util.ConcurrencyUtil;
19 import com.intellij.util.Time;
20 import com.intellij.util.concurrency.WorkerThread;
21 import com.intellij.util.containers.HashSet;
22 import com.intellij.util.enumeration.EnumerationCopy;
23 import com.intellij.util.ui.UIUtil;
24 import com.intellij.util.ui.tree.TreeUtil;
25 import com.intellij.util.ui.update.Activatable;
26 import com.intellij.util.ui.update.UiNotifyConnector;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
30 import javax.swing.*;
31 import javax.swing.event.TreeExpansionEvent;
32 import javax.swing.event.TreeExpansionListener;
33 import javax.swing.event.TreeSelectionEvent;
34 import javax.swing.event.TreeSelectionListener;
35 import javax.swing.tree.*;
36 import java.awt.*;
37 import java.awt.event.FocusAdapter;
38 import java.awt.event.FocusEvent;
39 import java.security.AccessControlException;
40 import java.util.*;
41 import java.util.List;
42 import java.util.concurrent.ScheduledExecutorService;
43 import java.util.concurrent.TimeUnit;
45 class AbstractTreeUi {
46 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
47 protected JTree myTree;// protected for TestNG
48 @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel;
49 private AbstractTreeStructure myTreeStructure;
50 private AbstractTreeUpdater myUpdater;
51 private Comparator<NodeDescriptor> myNodeDescriptorComparator;
52 private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() {
53 public int compare(TreeNode n1, TreeNode n2) {
54 if (isLoadingNode(n1) || isLoadingNode(n2)) return 0;
55 NodeDescriptor nodeDescriptor1 = (NodeDescriptor)((DefaultMutableTreeNode)n1).getUserObject();
56 NodeDescriptor nodeDescriptor2 = (NodeDescriptor)((DefaultMutableTreeNode)n2).getUserObject();
57 return myNodeDescriptorComparator != null
58 ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2)
59 : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex();
62 private DefaultMutableTreeNode myRootNode;
63 private final HashMap<Object, Object> myElementToNodeMap = new HashMap<Object, Object>();
64 private final HashSet<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>();
65 private TreeExpansionListener myExpansionListener;
66 private MySelectionListener mySelectionListener;
68 private WorkerThread myWorker = null;
69 private final ArrayList<Runnable> myActiveWorkerTasks = new ArrayList<Runnable>();
71 private ProgressIndicator myProgress;
72 private static final int WAIT_CURSOR_DELAY = 100;
73 private AbstractTreeNode<Object> TREE_NODE_WRAPPER;
74 private boolean myRootNodeWasInitialized = false;
75 private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>();
76 private boolean myUpdateFromRootRequested;
77 private boolean myWasEverShown;
78 private boolean myUpdateIfInactive;
79 private final List<Object> myLoadingParents = new ArrayList<Object>();
80 private long myClearOnHideDelay = -1;
81 private ScheduledExecutorService ourClearanceService;
82 private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
84 private final List<Runnable> myDeferredSelections = new ArrayList<Runnable>();
85 private final List<Runnable> myDeferredExpansions = new ArrayList<Runnable>();
87 private boolean myCanProcessDeferredSelections;
89 private UpdaterTreeState myUpdaterState;
90 private AbstractTreeBuilder myBuilder;
92 private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
93 private long myJanitorPollPeriod = Time.SECOND * 10;
94 private boolean myCheckStructure = false;
97 private boolean myCanYield = false;
99 private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
101 private boolean myYeildingNow;
103 private List<DefaultMutableTreeNode> myPendingNodeActions = new ArrayList<DefaultMutableTreeNode>();
104 private List<Runnable> myYeildingDoneRunnables = new ArrayList<Runnable>();
106 private Alarm myBusyAlarm = new Alarm();
107 private Runnable myWaiterForReady = new Runnable() {
108 public void run() {
109 maybeSetBusyAndScheduleWaiterForReady(false);
113 private RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
114 private RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
115 private RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimout");
117 private boolean myWasEverIndexNotReady;
118 private boolean myShowing;
120 protected final void init(AbstractTreeBuilder builder,
121 JTree tree,
122 DefaultTreeModel treeModel,
123 AbstractTreeStructure treeStructure,
124 @Nullable Comparator<NodeDescriptor> comparator) {
126 init(builder, tree, treeModel, treeStructure, comparator, true);
129 protected void init(AbstractTreeBuilder builder,
130 JTree tree,
131 DefaultTreeModel treeModel,
132 AbstractTreeStructure treeStructure,
133 @Nullable Comparator<NodeDescriptor> comparator,
134 boolean updateIfInactive) {
135 myBuilder = builder;
136 myTree = tree;
137 myTreeModel = treeModel;
138 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
139 myTree.setModel(myTreeModel);
140 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
141 setTreeStructure(treeStructure);
142 myNodeDescriptorComparator = comparator;
143 myUpdateIfInactive = updateIfInactive;
145 myExpansionListener = new MyExpansionListener();
146 myTree.addTreeExpansionListener(myExpansionListener);
148 mySelectionListener = new MySelectionListener();
149 myTree.addTreeSelectionListener(mySelectionListener);
151 setUpdater(getBuilder().createUpdater());
152 myProgress = getBuilder().createProgressIndicator();
153 Disposer.register(getBuilder(), getUpdater());
155 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
156 public void showNotify() {
157 if (!isReleased()) {
158 AbstractTreeUi.this.showNotify();
162 public void hideNotify() {
163 if (!isReleased()) {
164 AbstractTreeUi.this.hideNotify();
168 Disposer.register(getBuilder(), uiNotify);
170 myTree.addFocusListener(new FocusAdapter() {
171 @Override
172 public void focusGained(FocusEvent e) {
173 maybeReady();
178 protected void hideNotify() {
179 myShowing = false;
181 getUpdater().hideNotify();
183 myBusyAlarm.cancelAllRequests();
185 if (!myWasEverShown) return;
187 if (!myNodeActions.isEmpty()) {
188 cancelBackgroundLoading();
189 myUpdateFromRootRequested = true;
192 if (getClearOnHideDelay() >= 0) {
193 ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
194 initClearanceServiceIfNeeded();
198 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
199 if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
201 if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
202 final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
203 final boolean isBusy = !isReady() || forcedBusy;
204 if (isBusy && tree.isShowing()) {
205 tree.setPaintBusy(true);
206 myBusyAlarm.cancelAllRequests();
207 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
209 else {
210 tree.setPaintBusy(false);
215 private void initClearanceServiceIfNeeded() {
216 if (ourClearanceService != null) return;
218 ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor");
219 ourClearanceService.scheduleWithFixedDelay(new Runnable() {
220 public void run() {
221 cleanUpAll();
223 }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
226 private void cleanUpAll() {
227 final long now = System.currentTimeMillis();
228 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
229 for (AbstractTreeUi eachUi : uis) {
230 if (eachUi == null) continue;
231 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
232 if (timeToCleanup == null) continue;
233 if (now >= timeToCleanup.longValue()) {
234 ourUi2Countdown.remove(eachUi);
235 getBuilder().cleanUp();
240 protected void doCleanUp() {
241 final Application app = ApplicationManager.getApplication();
242 if (app != null && app.isUnitTestMode()) {
243 cleanUpNow();
245 else {
246 // we are not in EDT
247 //noinspection SSBasedInspection
248 SwingUtilities.invokeLater(new Runnable() {
249 public void run() {
250 if (!isReleased()) {
251 cleanUpNow();
258 private void disposeClearanceService() {
259 try {
260 if (ourClearanceService != null) {
261 ourClearanceService.shutdown();
262 ourClearanceService = null;
265 catch (AccessControlException e) {
266 LOG.warn(e);
270 void showNotify() {
271 myShowing = true;
273 myCanProcessDeferredSelections = true;
275 ourUi2Countdown.remove(this);
277 if (!myWasEverShown || myUpdateFromRootRequested) {
278 if (wasRootNodeInitialized()) {
279 getBuilder().updateFromRoot();
281 else {
282 initRootNodeNowIfNeeded(new TreeUpdatePass(getRootNode()));
283 getBuilder().updateFromRoot();
286 myWasEverShown = true;
288 getUpdater().showNotify();
291 public void release() {
292 if (isReleased()) return;
294 myTree.removeTreeExpansionListener(myExpansionListener);
295 myTree.removeTreeSelectionListener(mySelectionListener);
296 disposeNode(getRootNode());
297 myElementToNodeMap.clear();
298 getUpdater().cancelAllRequests();
299 if (myWorker != null) {
300 myWorker.dispose(true);
301 clearWorkerTasks();
303 TREE_NODE_WRAPPER.setValue(null);
304 if (myProgress != null) {
305 myProgress.cancel();
307 disposeClearanceService();
309 myTree = null;
310 setUpdater(null);
311 myWorker = null;
312 //todo [kirillk] afraid to do so just in release day, to uncomment
313 // myTreeStructure = null;
314 myBuilder = null;
316 myNodeActions.clear();
317 myPendingNodeActions.clear();
318 myDeferredSelections.clear();
319 myDeferredExpansions.clear();
320 myYeildingDoneRunnables.clear();
323 public boolean isReleased() {
324 return myBuilder == null;
327 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
328 getTreeStructure().commit();
329 addNodeAction(getElementFor(node), new NodeAction() {
330 public void onReady(final DefaultMutableTreeNode node) {
331 processSmartExpand(node);
334 getUpdater().addSubtreeToUpdate(node);
335 getUpdater().performUpdate();
338 public final AbstractTreeStructure getTreeStructure() {
339 return myTreeStructure;
342 public final JTree getTree() {
343 return myTree;
346 @Nullable
347 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
348 DefaultMutableTreeNode result = null;
349 if (validateAgainstStructure) {
350 int index = 0;
351 while (true) {
352 final DefaultMutableTreeNode node = findNode(element, index);
353 if (node == null) break;
355 if (isNodeValidForElement(element, node)) {
356 result = node;
357 break;
360 index++;
363 else {
364 result = getFirstNode(element);
368 if (result != null && !isNodeInStructure(result)) {
369 disposeNode(result);
370 result = null;
373 return result;
376 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
377 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
380 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
381 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
384 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
385 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
386 final Object parentElement = getElementFor(parent);
387 if (!isInStructure(parentElement)) return false;
389 if (parent instanceof ElementNode) {
390 return ((ElementNode)parent).isValidChild(element);
392 else {
393 for (int i = 0; i < parent.getChildCount(); i++) {
394 final TreeNode child = parent.getChildAt(i);
395 final Object eachElement = getElementFor(child);
396 if (element.equals(eachElement)) return true;
400 return false;
403 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
404 boolean valid = true;
405 while (true) {
406 if (eachParent == null) {
407 valid = eachParentNode == null;
408 break;
411 if (!eachParent.equals(getElementFor(eachParentNode))) {
412 valid = false;
413 break;
416 eachParent = getTreeStructure().getParentElement(eachParent);
417 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
419 return valid;
422 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
423 DefaultMutableTreeNode node = null;
424 for (final Object pathElement : path) {
425 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
426 if (node == null) {
427 break;
430 return node;
433 public final void buildNodeForElement(Object element) {
434 getUpdater().performUpdate();
435 DefaultMutableTreeNode node = getNodeForElement(element, false);
436 if (node == null) {
437 final java.util.List<Object> elements = new ArrayList<Object>();
438 while (true) {
439 element = getTreeStructure().getParentElement(element);
440 if (element == null) {
441 break;
443 elements.add(0, element);
446 for (final Object element1 : elements) {
447 node = getNodeForElement(element1, false);
448 if (node != null) {
449 expand(node);
455 public final void buildNodeForPath(Object[] path) {
456 getUpdater().performUpdate();
457 DefaultMutableTreeNode node = null;
458 for (final Object pathElement : path) {
459 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
460 if (node != null && node != path[path.length - 1]) {
461 expand(node);
466 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
467 myNodeDescriptorComparator = nodeDescriptorComparator;
468 List<Object> pathsToExpand = new ArrayList<Object>();
469 List<Object> selectionPaths = new ArrayList<Object>();
470 TreeBuilderUtil.storePaths(getBuilder(), getRootNode(), pathsToExpand, selectionPaths, false);
471 resortChildren(getRootNode());
472 myTreeModel.nodeStructureChanged(getRootNode());
473 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
476 protected AbstractTreeBuilder getBuilder() {
477 return myBuilder;
480 private void resortChildren(DefaultMutableTreeNode node) {
481 ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
482 node.removeAllChildren();
483 Collections.sort(childNodes, myNodeComparator);
484 for (TreeNode childNode1 : childNodes) {
485 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)childNode1;
486 node.add(childNode);
487 resortChildren(childNode);
491 protected final void initRootNode() {
492 final Activatable activatable = new Activatable() {
493 public void showNotify() {
494 if (!myRootNodeWasInitialized) {
495 initRootNodeNowIfNeeded(new TreeUpdatePass(getRootNode()));
499 public void hideNotify() {
503 if (myUpdateIfInactive || ApplicationManager.getApplication().isUnitTestMode()) {
504 activatable.showNotify();
506 else {
507 new UiNotifyConnector.Once(myTree, activatable);
511 private void initRootNodeNowIfNeeded(TreeUpdatePass pass) {
512 if (myRootNodeWasInitialized) return;
514 myRootNodeWasInitialized = true;
515 Object rootElement = getTreeStructure().getRootElement();
516 addNodeAction(rootElement, new NodeAction() {
517 public void onReady(final DefaultMutableTreeNode node) {
518 processDeferredActions();
521 NodeDescriptor nodeDescriptor = getTreeStructure().createDescriptor(rootElement, null);
522 getRootNode().setUserObject(nodeDescriptor);
523 update(nodeDescriptor, false);
524 if (getElementFromDescriptor(nodeDescriptor) != null) {
525 createMapping(getElementFromDescriptor(nodeDescriptor), getRootNode());
527 addLoadingNode(getRootNode());
528 boolean willUpdate = false;
529 if (getBuilder().isAutoExpandNode(nodeDescriptor)) {
530 willUpdate = myUnbuiltNodes.contains(getRootNode());
531 expand(getRootNode());
533 if (!willUpdate) {
534 updateNodeChildren(getRootNode(), pass, null, false);
536 if (getRootNode().getChildCount() == 0) {
537 myTreeModel.nodeChanged(getRootNode());
540 if (!isLoadedInBackground(getTreeStructure().getRootElement())) {
541 processDeferredActions();
545 private boolean update(final NodeDescriptor nodeDescriptor, boolean canBeNonEdt) {
546 if (!canBeNonEdt && myWasEverShown) {
547 assertIsDispatchThread();
550 if (isEdt() || !myWasEverShown) {
551 return getBuilder().updateNodeDescriptor(nodeDescriptor);
553 else {
554 UIUtil.invokeLaterIfNeeded(new Runnable() {
555 public void run() {
556 if (!isReleased()) {
557 getBuilder().updateNodeDescriptor(nodeDescriptor);
561 return true;
565 private void assertIsDispatchThread() {
566 if (isTreeShowing() && !isEdt()) {
567 LOG.error("Must be in event-dispatch thread");
571 private boolean isEdt() {
572 return SwingUtilities.isEventDispatchThread();
575 private boolean isTreeShowing() {
576 return myShowing;
579 private void assertNotDispatchThread() {
580 if (isEdt()) {
581 LOG.error("Must not be in event-dispatch thread");
585 private void processDeferredActions() {
586 processDeferredActions(myDeferredSelections);
587 processDeferredActions(myDeferredExpansions);
590 private void processDeferredActions(List<Runnable> actions) {
591 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
592 actions.clear();
593 for (Runnable runnable : runnables) {
594 runnable.run();
598 public void doUpdateFromRoot() {
599 updateSubtree(getRootNode());
602 public ActionCallback doUpdateFromRootCB() {
603 final ActionCallback cb = new ActionCallback();
604 getUpdater().runAfterUpdate(new Runnable() {
605 public void run() {
606 cb.setDone();
609 updateSubtree(getRootNode());
610 return cb;
613 public final void updateSubtree(DefaultMutableTreeNode node) {
614 updateSubtree(new TreeUpdatePass(node));
617 public final void updateSubtree(TreeUpdatePass pass) {
618 if (getUpdater() != null) {
619 getUpdater().addSubtreeToUpdate(pass);
620 } else {
621 updateSubtreeNow(pass);
625 final void updateSubtreeNow(TreeUpdatePass pass) {
626 maybeSetBusyAndScheduleWaiterForReady(true);
628 initRootNodeNowIfNeeded(pass);
630 final DefaultMutableTreeNode node = pass.getNode();
632 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
634 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
636 final NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
638 if (!istToBuildInBackground(descriptor)) {
639 getBuilder().updateNode(node);
642 updateNodeChildren(node, pass, null, false);
645 private boolean istToBuildInBackground(NodeDescriptor descriptor) {
646 return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor));
649 @NotNull
650 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
651 final UpdaterTreeState oldState = myUpdaterState;
652 if (oldState == null) {
653 myUpdaterState = state;
654 return state;
656 else {
657 oldState.addAll(state);
658 return oldState;
662 protected void doUpdateNode(DefaultMutableTreeNode node) {
663 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
664 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
665 Object prevElement = getElementFromDescriptor(descriptor);
666 if (prevElement == null) return;
667 boolean changes = update(descriptor, false);
668 if (!isValid(descriptor)) {
669 if (isInStructure(prevElement)) {
670 getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement));
671 return;
674 if (changes) {
675 updateNodeImageAndPosition(node);
679 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
680 return getBuilder().getTreeStructureElement(descriptor);
683 private void updateNodeChildren(final DefaultMutableTreeNode node,
684 final TreeUpdatePass pass,
685 @Nullable Object[] preloadedChildren,
686 boolean forcedNow) {
687 getTreeStructure().commit();
688 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath()));
689 final boolean wasLeaf = node.getChildCount() == 0;
691 final NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
693 if (descriptor == null) return;
695 if (myUnbuiltNodes.contains(node)) {
696 processUnbuilt(node, descriptor, pass);
697 processNodeActionsIfReady(node);
698 return;
701 if (!forcedNow && istToBuildInBackground(descriptor)) {
702 queueBackgroundUpdate(node, descriptor, pass);
703 return;
706 final MutualMap<Object, Integer> elementToIndexMap = collectElementToIndexMap(descriptor, preloadedChildren);
708 myUpdatingChildren.add(node);
709 pass.setCurrentNode(node);
710 processAllChildren(node, elementToIndexMap, pass).doWhenDone(new Runnable() {
711 public void run() {
712 if (isDisposed(node)) {
713 return;
716 if (canYield()) {
717 removeLoading(node, false);
720 ArrayList<TreeNode> nodesToInsert = collectNodesToInsert(descriptor, elementToIndexMap);
722 insertNodesInto(nodesToInsert, node);
724 updateNodesToInsert(nodesToInsert, pass);
726 if (wasExpanded) {
727 expand(node);
730 if (wasExpanded || wasLeaf) {
731 expand(node, descriptor, wasLeaf);
734 myUpdatingChildren.remove(node);
736 final Object element = getElementFor(node);
737 addNodeAction(element, new NodeAction() {
738 public void onReady(final DefaultMutableTreeNode node) {
739 removeLoading(node, false);
743 processNodeActionsIfReady(node);
748 private boolean isDisposed(DefaultMutableTreeNode node) {
749 return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
752 private void expand(DefaultMutableTreeNode node) {
753 expand(new TreePath(node.getPath()));
756 private void expand(final TreePath path) {
757 if (path == null) return;
758 final Object last = path.getLastPathComponent();
759 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
760 final boolean isRoot = last == myTree.getModel().getRoot();
761 final TreePath parent = path.getParentPath();
762 if (isRoot && !myTree.isExpanded(path)) {
763 insertLoadingNode((DefaultMutableTreeNode)last, true);
764 expandPath(path);
765 } else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last))) {
766 if (last instanceof DefaultMutableTreeNode) {
767 processNodeActionsIfReady((DefaultMutableTreeNode)last);
770 else {
771 if (isLeaf && myUnbuiltNodes.contains(last)) {
772 insertLoadingNode((DefaultMutableTreeNode)last, true);
773 expandPath(path);
774 } else if (isLeaf && parent != null) {
775 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
776 if (parentNode != null) {
777 addToUnbuilt(parentNode);
779 expandPath(parent);
781 else {
782 expandPath(path);
787 private void addToUnbuilt(DefaultMutableTreeNode node) {
788 myUnbuiltNodes.add(node);
791 private void removeFromUnbuilt(DefaultMutableTreeNode node) {
792 myUnbuiltNodes.remove(node);
795 private boolean processUnbuilt(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final TreeUpdatePass pass) {
796 if (getBuilder().isAlwaysShowPlus(descriptor)) return false; // check for isAlwaysShowPlus is important for e.g. changing Show Members state!
798 final Object element = getBuilder().getTreeStructureElement(descriptor);
800 if (getTreeStructure().isToBuildChildrenInBackground(element)) return false; //?
802 final Object[] children = getChildrenFor(element);
803 if (children.length == 0) {
804 removeLoading(node, false);
806 else if (getBuilder().isAutoExpandNode((NodeDescriptor)node.getUserObject())) {
807 addNodeAction(getElementFor(node), new NodeAction() {
808 public void onReady(final DefaultMutableTreeNode node) {
809 final TreePath path = new TreePath(node.getPath());
810 if (getTree().isExpanded(path) || children.length == 0) {
811 removeLoading(node, false);
813 else {
814 maybeYeild(new ActiveRunnable() {
815 public ActionCallback run() {
816 expand(element, null);
817 return new ActionCallback.Done();
819 }, pass, node);
825 return true;
828 private boolean removeIfLoading(TreeNode node) {
829 if (isLoadingNode(node)) {
830 removeNodeFromParent((MutableTreeNode)node, false);
831 return true;
834 return false;
837 //todo [kirillk] temporary consistency check
838 private Object[] getChildrenFor(final Object element) {
839 final Object[] passOne;
840 try {
841 passOne = getTreeStructure().getChildElements(element);
843 catch (IndexNotReadyException e) {
844 if (!myWasEverIndexNotReady) {
845 myWasEverIndexNotReady = true;
846 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
848 return ArrayUtil.EMPTY_OBJECT_ARRAY;
851 if (!myCheckStructure) return passOne;
853 final Object[] passTwo = getTreeStructure().getChildElements(element);
855 final HashSet two = new HashSet(Arrays.asList(passTwo));
857 if (passOne.length != passTwo.length) {
858 LOG.error(
859 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
860 element);
862 else {
863 for (Object eachInOne : passOne) {
864 if (!two.contains(eachInOne)) {
865 LOG.error(
866 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
867 element);
868 break;
873 return passOne;
876 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert, TreeUpdatePass pass) {
877 for (TreeNode aNodesToInsert : nodesToInsert) {
878 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
879 addLoadingNode(childNode);
880 updateNodeChildren(childNode, pass, null, false);
884 private ActionCallback processAllChildren(final DefaultMutableTreeNode node,
885 final MutualMap<Object, Integer> elementToIndexMap,
886 final TreeUpdatePass pass) {
888 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
889 return maybeYeild(new ActiveRunnable() {
890 public ActionCallback run() {
891 return processAllChildren(node, elementToIndexMap, pass, childNodes);
893 }, pass, node);
896 private boolean isRerunNeeded(TreeUpdatePass pass) {
897 if (pass.isExpired()) return false;
899 final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
901 return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
904 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
905 final ActionCallback result = new ActionCallback();
907 if (isRerunNeeded(pass)) {
908 getUpdater().addSubtreeToUpdate(pass);
909 result.setRejected();
910 } else {
911 if (isToYieldUpdateFor(node)) {
912 pass.setCurrentNode(node);
913 yieldAndRun(new Runnable() {
914 public void run() {
915 if (pass.isExpired()) return;
917 if (isRerunNeeded(pass)) {
918 runDone(new Runnable() {
919 public void run() {
920 if (!pass.isExpired()) {
921 getUpdater().addSubtreeToUpdate(pass);
925 result.setRejected();
927 else {
928 processRunnable.run().notify(result);
931 }, pass);
933 else {
934 processRunnable.run().notify(result);
938 return result;
941 private void yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
942 myYeildingPasses.add(pass);
943 myYeildingNow = true;
944 yield(new Runnable() {
945 public void run() {
946 if (isReleased()) {
947 return;
950 runOnYieldingDone(new Runnable() {
951 public void run() {
952 if (isReleased()) {
953 return;
955 executeYieldingRequest(runnable, pass);
962 public boolean isYeildingNow() {
963 return myYeildingNow;
966 private boolean hasSheduledUpdates() {
967 return getUpdater().hasNodesToUpdate() || isLoadingInBackground();
970 private boolean hasExpandedUnbuiltNodes() {
971 for (DefaultMutableTreeNode each : myUnbuiltNodes) {
972 if (myTree.isExpanded(new TreePath(each.getPath()))) return true;
975 return false;
978 public boolean isReady() {
979 return !isYeildingNow() && !isWorkerBusy() && (!hasSheduledUpdates() || getUpdater().isInPostponeMode()) && !hasExpandedUnbuiltNodes();
982 private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
983 try {
984 myYeildingPasses.remove(pass);
985 runnable.run();
987 finally {
988 maybeYeildingFinished();
992 private void maybeYeildingFinished() {
993 if (myYeildingPasses.size() == 0) {
994 myYeildingNow = false;
995 flushPendingNodeActions();
999 void maybeReady() {
1000 if (isReady()) {
1001 if (myTree.isShowing()) {
1002 if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
1003 TreeUtil.ensureSelection(myTree);
1009 private void flushPendingNodeActions() {
1010 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
1011 myPendingNodeActions.clear();
1013 for (DefaultMutableTreeNode each : nodes) {
1014 processNodeActionsIfReady(each);
1017 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
1018 for (Runnable each : actions) {
1019 if (!isYeildingNow()) {
1020 myYeildingDoneRunnables.remove(each);
1021 each.run();
1025 maybeReady();
1028 protected void runOnYieldingDone(Runnable onDone) {
1029 getBuilder().runOnYeildingDone(onDone);
1032 protected void yield(Runnable runnable) {
1033 getBuilder().yield(runnable);
1036 private ActionCallback processAllChildren(final DefaultMutableTreeNode node,
1037 final MutualMap<Object, Integer> elementToIndexMap,
1038 final TreeUpdatePass pass,
1039 final ArrayList<TreeNode> childNodes) {
1042 if (pass.isExpired()) return new ActionCallback.Rejected();
1044 if (childNodes.size() == 0) return new ActionCallback.Done();
1047 final ActionCallback result = new ActionCallback(childNodes.size());
1049 for (TreeNode childNode1 : childNodes) {
1050 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)childNode1;
1051 if (isLoadingNode(childNode)) {
1052 result.setDone();
1053 continue;
1056 maybeYeild(new ActiveRunnable() {
1057 @Override
1058 public ActionCallback run() {
1059 return processChildNode(childNode, (NodeDescriptor)childNode.getUserObject(), node, elementToIndexMap, pass);
1061 }, pass, node).notify(result);
1063 if (result.isRejected()) break;
1066 return result;
1069 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
1070 if (!canYield()) return false;
1071 return getBuilder().isToYieldUpdateFor(node);
1074 private MutualMap<Object, Integer> collectElementToIndexMap(final NodeDescriptor descriptor, @Nullable Object[] preloadedChildren) {
1075 MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
1076 Object[] children = preloadedChildren != null ? preloadedChildren : getChildrenFor(getBuilder().getTreeStructureElement(descriptor));
1077 int index = 0;
1078 for (Object child : children) {
1079 if (!isValid(child)) continue;
1080 elementToIndexMap.put(child, Integer.valueOf(index));
1081 index++;
1083 return elementToIndexMap;
1086 private void expand(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final boolean wasLeaf) {
1087 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1088 alarm.addRequest(new Runnable() {
1089 public void run() {
1090 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1092 }, WAIT_CURSOR_DELAY);
1094 if (wasLeaf && getBuilder().isAutoExpandNode(descriptor)) {
1095 expand(node);
1098 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1099 for (TreeNode node1 : nodes) {
1100 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1101 if (isLoadingNode(childNode)) continue;
1102 NodeDescriptor childDescr = (NodeDescriptor)childNode.getUserObject();
1103 if (getBuilder().isAutoExpandNode(childDescr)) {
1104 addNodeAction(getElementFor(childNode), new NodeAction() {
1105 public void onReady(DefaultMutableTreeNode node) {
1106 expand(childNode);
1109 addSubtreeToUpdate(childNode);
1113 int n = alarm.cancelAllRequests();
1114 if (n == 0) {
1115 myTree.setCursor(Cursor.getDefaultCursor());
1119 public static boolean isLoadingNode(final Object node) {
1120 return node instanceof LoadingNode;
1123 private ArrayList<TreeNode> collectNodesToInsert(final NodeDescriptor descriptor, final MutualMap<Object, Integer> elementToIndexMap) {
1124 ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1125 final Collection<Object> allElements = elementToIndexMap.getKeys();
1126 for (Object child : allElements) {
1127 Integer index = elementToIndexMap.getValue(child);
1128 final NodeDescriptor childDescr = getTreeStructure().createDescriptor(child, descriptor);
1129 //noinspection ConstantConditions
1130 if (childDescr == null) {
1131 LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1132 continue;
1134 childDescr.setIndex(index.intValue());
1135 update(childDescr, false);
1136 if (getElementFromDescriptor(childDescr) == null) {
1137 LOG.error("childDescr.getElement() == null, child = " + child + ", builder = " + this);
1138 continue;
1140 final DefaultMutableTreeNode childNode = createChildNode(childDescr);
1141 nodesToInsert.add(childNode);
1142 createMapping(getElementFromDescriptor(childDescr), childNode);
1145 return nodesToInsert;
1148 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1149 return new ElementNode(this, descriptor);
1152 protected boolean canYield() {
1153 return myCanYield && myYeildingUpdate.asBoolean();
1156 public long getClearOnHideDelay() {
1157 return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
1160 static class ElementNode extends DefaultMutableTreeNode {
1162 Set<Object> myElements = new HashSet<Object>();
1163 AbstractTreeUi myUi;
1166 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1167 super(descriptor);
1168 myUi = ui;
1171 @Override
1172 public void insert(final MutableTreeNode newChild, final int childIndex) {
1173 super.insert(newChild, childIndex);
1174 final Object element = myUi.getElementFor(newChild);
1175 if (element != null) {
1176 myElements.add(element);
1180 @Override
1181 public void remove(final int childIndex) {
1182 final TreeNode node = getChildAt(childIndex);
1183 super.remove(childIndex);
1184 final Object element = myUi.getElementFor(node);
1185 if (element != null) {
1186 myElements.remove(element);
1190 boolean isValidChild(Object childElement) {
1191 return myElements.contains(childElement);
1194 @Override
1195 public String toString() {
1196 return String.valueOf(getUserObject());
1200 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1201 DefaultMutableTreeNode eachParent = kid;
1202 while (eachParent != null) {
1203 if (myUpdatingChildren.contains(eachParent)) return true;
1204 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1207 return false;
1210 private boolean isLoadedInBackground(Object element) {
1211 synchronized (myLoadingParents) {
1212 return myLoadingParents.contains(element);
1216 private void addToLoadedInBackground(Object element) {
1217 synchronized (myLoadingParents) {
1218 myLoadingParents.add(element);
1222 private void removeFromLoadedInBackground(Object element) {
1223 synchronized (myLoadingParents) {
1224 myLoadingParents.remove(element);
1228 private boolean isLoadingInBackground() {
1229 synchronized (myLoadingParents) {
1230 return myLoadingParents.size() > 0;
1234 private boolean queueBackgroundUpdate(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final TreeUpdatePass pass) {
1235 assertIsDispatchThread();
1237 final Object oldElementFromDescriptor = getElementFromDescriptor(descriptor);
1239 if (isLoadedInBackground(oldElementFromDescriptor)) return false;
1241 addToLoadedInBackground(oldElementFromDescriptor);
1243 if (!isNodeBeingBuilt(node)) {
1244 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1245 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1248 final Ref<Object[]> children = new Ref<Object[]>();
1249 final Ref<Object> elementFromDescriptor = new Ref<Object>();
1250 Runnable buildRunnable = new Runnable() {
1251 public void run() {
1252 if (isReleased()) {
1253 return;
1256 update(descriptor, true);
1257 Object element = getElementFromDescriptor(descriptor);
1258 if (element == null) {
1259 removeFromLoadedInBackground(oldElementFromDescriptor);
1260 return;
1263 elementFromDescriptor.set(element);
1264 children.set(getChildrenFor(getBuilder().getTreeStructureElement(descriptor))); // load children
1268 final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
1269 Runnable updateRunnable = new Runnable() {
1270 public void run() {
1271 if (isReleased()) return;
1272 if (children.get() == null) return;
1274 if (isRerunNeeded(pass)) {
1275 removeFromLoadedInBackground(elementFromDescriptor.get());
1276 getUpdater().addSubtreeToUpdate(pass);
1277 return;
1280 removeFromLoadedInBackground(elementFromDescriptor.get());
1281 updateNodeChildren(node, pass, children.get(), true);
1283 if (isRerunNeeded(pass)) {
1284 getUpdater().addSubtreeToUpdate(pass);
1285 return;
1288 Object element = elementFromDescriptor.get();
1290 if (element != null) {
1291 removeLoading(node, true);
1293 nodeToProcessActions[0] = node;
1297 addTaskToWorker(buildRunnable, true, updateRunnable, new Runnable() {
1298 public void run() {
1299 if (nodeToProcessActions[0] != null) {
1300 processNodeActionsIfReady(nodeToProcessActions[0]);
1304 return true;
1307 private void removeLoading(DefaultMutableTreeNode parent, boolean removeFromUnbuilt) {
1308 for (int i = 0; i < parent.getChildCount(); i++) {
1309 TreeNode child = parent.getChildAt(i);
1310 if (removeIfLoading(child)) {
1311 i--;
1315 if (removeFromUnbuilt) {
1316 removeFromUnbuilt(parent);
1319 maybeReady();
1322 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1323 if (isNodeBeingBuilt(node)) return;
1325 final Object o = node.getUserObject();
1326 if (!(o instanceof NodeDescriptor)) return;
1329 if (isYeildingNow()) {
1330 myPendingNodeActions.add(node);
1331 return;
1334 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1336 final List<NodeAction> actions = myNodeActions.get(element);
1337 if (actions != null) {
1338 myNodeActions.remove(element);
1339 for (NodeAction each : actions) {
1340 each.onReady(node);
1344 if (!isUpdatingParent(node) && !isWorkerBusy()) {
1345 final UpdaterTreeState state = myUpdaterState;
1346 if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1347 if (!state.restore()) {
1348 setUpdaterState(state);
1353 maybeReady();
1357 private void processSmartExpand(final DefaultMutableTreeNode node) {
1358 if (getBuilder().isSmartExpand() && node.getChildCount() == 1) { // "smart" expand
1359 TreeNode childNode = node.getChildAt(0);
1360 if (isLoadingNode(childNode)) return;
1361 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(childNode);
1362 expand(childPath);
1366 public boolean isLoadingChildrenFor(final Object nodeObject) {
1367 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1369 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1371 int loadingNodes = 0;
1372 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
1373 TreeNode child = node.getChildAt(i);
1374 if (isLoadingNode(child)) {
1375 loadingNodes++;
1378 return loadingNodes > 0 && loadingNodes == node.getChildCount();
1381 private boolean isParentLoading(Object nodeObject) {
1382 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1384 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1386 TreeNode eachParent = node.getParent();
1388 while (eachParent != null) {
1389 eachParent = eachParent.getParent();
1390 if (eachParent instanceof DefaultMutableTreeNode) {
1391 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
1392 if (isLoadedInBackground(eachElement)) return true;
1396 return false;
1399 protected String getLoadingNodeText() {
1400 return IdeBundle.message("progress.searching");
1403 private ActionCallback processChildNode(final DefaultMutableTreeNode childNode,
1404 final NodeDescriptor childDescriptor,
1405 final DefaultMutableTreeNode parentNode,
1406 final MutualMap<Object, Integer> elementToIndexMap,
1407 TreeUpdatePass pass) {
1409 if (pass.isExpired()) {
1410 return new ActionCallback.Rejected();
1413 NodeDescriptor childDesc = childDescriptor;
1416 if (childDesc == null) {
1417 pass.expire();
1418 return new ActionCallback.Rejected();
1420 Object oldElement = getElementFromDescriptor(childDesc);
1421 if (oldElement == null) {
1422 pass.expire();
1423 return new ActionCallback.Rejected();
1425 boolean changes = update(childDesc, false);
1426 boolean forceRemapping = false;
1427 Object newElement = getElementFromDescriptor(childDesc);
1429 Integer index = newElement != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc)) : null;
1430 if (index != null) {
1431 final Object elementFromMap = elementToIndexMap.getKey(index);
1432 if (elementFromMap != newElement && elementFromMap.equals(newElement)) {
1433 if (isInStructure(elementFromMap) && isInStructure(newElement)) {
1434 if (parentNode.getUserObject() instanceof NodeDescriptor) {
1435 final NodeDescriptor parentDescriptor = (NodeDescriptor)parentNode.getUserObject();
1436 childDesc = getTreeStructure().createDescriptor(elementFromMap, parentDescriptor);
1437 childNode.setUserObject(childDesc);
1438 newElement = elementFromMap;
1439 forceRemapping = true;
1440 update(childDesc, false);
1441 changes = true;
1446 if (childDesc.getIndex() != index.intValue()) {
1447 changes = true;
1449 childDesc.setIndex(index.intValue());
1452 if (index != null && changes) {
1453 updateNodeImageAndPosition(childNode);
1455 if (!oldElement.equals(newElement) | forceRemapping) {
1456 removeMapping(oldElement, childNode, newElement);
1457 if (newElement != null) {
1458 createMapping(newElement, childNode);
1462 if (index == null) {
1463 int selectedIndex = -1;
1464 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
1465 selectedIndex = parentNode.getIndex(childNode);
1468 if (childNode.getParent() instanceof DefaultMutableTreeNode) {
1469 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
1470 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
1471 if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
1472 insertLoadingNode(parent, false);
1477 Object disposedElement = getElementFor(childNode);
1479 removeNodeFromParent(childNode, selectedIndex >= 0);
1480 disposeNode(childNode);
1482 if (selectedIndex >= 0) {
1483 if (parentNode.getChildCount() > 0) {
1484 if (parentNode.getChildCount() > selectedIndex) {
1485 TreeNode newChildNode = parentNode.getChildAt(selectedIndex);
1486 if (isValidForSelectionAdjusting(newChildNode)) {
1487 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement));
1490 else {
1491 TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1);
1492 if (isValidForSelectionAdjusting(newChild)) {
1493 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement));
1497 else {
1498 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement));
1502 else {
1503 elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc));
1504 updateNodeChildren(childNode, pass, null, false);
1507 if (parentNode.equals(getRootNode())) {
1508 myTreeModel.nodeChanged(getRootNode());
1511 return new ActionCallback.Done();
1514 private boolean isValidForSelectionAdjusting(TreeNode node) {
1515 if (isLoadingNode(node)) return true;
1517 final Object elementInTree = getElementFor(node);
1518 if (elementInTree == null) return false;
1520 final TreeNode parentNode = node.getParent();
1521 final Object parentElementInTree = getElementFor(parentNode);
1522 if (parentElementInTree == null) return false;
1524 final Object parentElement = getTreeStructure().getParentElement(elementInTree);
1526 return parentElementInTree.equals(parentElement);
1529 private Condition getExpiredElementCondition(final Object element) {
1530 return new Condition() {
1531 public boolean value(final Object o) {
1532 return isInStructure(element);
1537 private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement) {
1538 doWithUpdaterState(new Runnable() {
1539 public void run() {
1540 TreePath toSelect = null;
1542 if (isLoadingNode(path.getLastPathComponent())) {
1543 final TreePath parentPath = path.getParentPath();
1544 if (parentPath != null) {
1545 toSelect = parentPath;
1548 else {
1549 toSelect = path;
1552 if (toSelect != null) {
1553 myTree.addSelectionPath(toSelect);
1555 if (isAdjustedSelection && myUpdaterState != null) {
1556 final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
1557 myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement);
1564 private static TreePath getPathFor(TreeNode node) {
1565 if (node instanceof DefaultMutableTreeNode) {
1566 return new TreePath(((DefaultMutableTreeNode)node).getPath());
1568 else {
1569 ArrayList nodes = new ArrayList();
1570 TreeNode eachParent = node;
1571 while (eachParent != null) {
1572 nodes.add(eachParent);
1573 eachParent = eachParent.getParent();
1576 return new TreePath(ArrayUtil.toObjectArray(nodes));
1581 private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
1582 doWithUpdaterState(new Runnable() {
1583 public void run() {
1584 if (willAdjustSelection) {
1585 final TreePath path = getPathFor(node);
1586 if (myTree.isPathSelected(path)) {
1587 myTree.removeSelectionPath(path);
1591 myTreeModel.removeNodeFromParent(node);
1596 private void expandPath(final TreePath path) {
1597 doWithUpdaterState(new Runnable() {
1598 public void run() {
1599 myTree.expandPath(path);
1604 private void doWithUpdaterState(Runnable runnable) {
1605 if (myUpdaterState != null) {
1606 myUpdaterState.process(runnable);
1608 else {
1609 runnable.run();
1613 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
1614 return descriptor.update();
1617 private void addLoadingNode(final DefaultMutableTreeNode node) {
1618 final boolean[] hasNoChildren = new boolean[1];
1619 final NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1620 if (!getBuilder().isAlwaysShowPlus(descriptor)) {
1621 Runnable updateRunnable = new Runnable() {
1622 public void run() {
1623 if (isReleased()) return;
1625 if (hasNoChildren[0]) {
1626 update(descriptor, false);
1627 removeLoading(node, false);
1632 if (istToBuildInBackground(descriptor)) {
1633 Runnable buildRunnable = new Runnable() {
1634 public void run() {
1635 if (isReleased()) return;
1637 update(descriptor, true);
1638 Object element = getBuilder().getTreeStructureElement(descriptor);
1639 if (element == null && !isValid(element)) return;
1641 Object[] children = getChildrenFor(element);
1642 hasNoChildren[0] = children.length == 0;
1645 addTaskToWorker(buildRunnable, false, updateRunnable, new Runnable() {
1646 public void run() {
1647 processNodeActionsIfReady(node);
1651 else {
1652 Object[] children = getChildrenFor(getBuilder().getTreeStructureElement(descriptor));
1653 if (children.length == 0) return;
1657 insertLoadingNode(node, true);
1661 private boolean isValid(DefaultMutableTreeNode node) {
1662 if (node == null) return false;
1663 final Object object = node.getUserObject();
1664 if (object instanceof NodeDescriptor) {
1665 return isValid((NodeDescriptor)object);
1668 return false;
1671 private boolean isValid(NodeDescriptor descriptor) {
1672 if (descriptor == null) return false;
1673 return isValid(getElementFromDescriptor(descriptor));
1676 private boolean isValid(Object element) {
1677 if (element instanceof ValidateableNode) {
1678 if (!((ValidateableNode)element).isValid()) return false;
1680 return getBuilder().validateNode(element);
1683 private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
1684 myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
1685 if (addToUnbuilt) {
1686 addToUnbuilt(node);
1691 protected void addTaskToWorker(@NotNull final Runnable bgReadActionRunnable,
1692 boolean first,
1693 @Nullable final Runnable edtPostRunnable,
1694 @Nullable final Runnable finalizeEdtRunnable) {
1695 registerWorkerTask(bgReadActionRunnable);
1697 final Runnable pooledThreadWithProgressRunnable = new Runnable() {
1698 public void run() {
1699 if (isReleased()) {
1700 return;
1703 final AbstractTreeBuilder builder = getBuilder();
1705 builder.runBackgroundLoading(new Runnable() {
1706 public void run() {
1707 assertNotDispatchThread();
1709 if (isReleased()) {
1710 return;
1713 try {
1714 bgReadActionRunnable.run();
1716 if (edtPostRunnable != null && !isReleased()) {
1717 builder.updateAfterLoadedInBackground(new Runnable() {
1718 public void run() {
1719 try {
1720 assertIsDispatchThread();
1722 if (isReleased()) {
1723 return;
1726 edtPostRunnable.run();
1728 finally {
1729 unregisterWorkerTask(bgReadActionRunnable, finalizeEdtRunnable);
1734 else {
1735 unregisterWorkerTask(bgReadActionRunnable, finalizeEdtRunnable);
1738 catch (ProcessCanceledException e) {
1739 unregisterWorkerTask(bgReadActionRunnable, finalizeEdtRunnable);
1741 catch (Throwable t) {
1742 unregisterWorkerTask(bgReadActionRunnable, finalizeEdtRunnable);
1743 throw new RuntimeException(t);
1750 Runnable pooledThreadRunnable = new Runnable() {
1751 public void run() {
1752 if (isReleased()) return;
1754 try {
1755 if (myProgress != null) {
1756 ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress);
1758 else {
1759 pooledThreadWithProgressRunnable.run();
1762 catch (ProcessCanceledException e) {
1763 //ignore
1768 if (myWorker == null || myWorker.isDisposed()) {
1769 myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
1770 myWorker.start();
1771 if (first) {
1772 myWorker.addTaskFirst(pooledThreadRunnable);
1774 else {
1775 myWorker.addTask(pooledThreadRunnable);
1777 myWorker.dispose(false);
1779 else {
1780 if (first) {
1781 myWorker.addTaskFirst(pooledThreadRunnable);
1783 else {
1784 myWorker.addTask(pooledThreadRunnable);
1789 private void registerWorkerTask(Runnable runnable) {
1790 synchronized (myActiveWorkerTasks) {
1791 myActiveWorkerTasks.add(runnable);
1795 private void unregisterWorkerTask(Runnable runnable, @Nullable Runnable finalizeRunnable) {
1796 boolean wasRemoved;
1797 synchronized (myActiveWorkerTasks) {
1798 wasRemoved = myActiveWorkerTasks.remove(runnable);
1801 if (wasRemoved && finalizeRunnable != null) {
1802 finalizeRunnable.run();
1805 maybeReady();
1808 public boolean isWorkerBusy() {
1809 synchronized (myActiveWorkerTasks) {
1810 return myActiveWorkerTasks.size() > 0;
1814 private void clearWorkerTasks() {
1815 synchronized (myActiveWorkerTasks) {
1816 myActiveWorkerTasks.clear();
1820 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node) {
1821 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
1822 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1823 if (getElementFromDescriptor(descriptor) == null) return;
1824 DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
1825 if (parentNode != null) {
1826 int oldIndex = parentNode.getIndex(node);
1827 int newIndex = oldIndex;
1828 if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
1829 final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
1830 for (int i = 0; i < parentNode.getChildCount(); i++) {
1831 children.add(parentNode.getChildAt(i));
1833 Collections.sort(children, myNodeComparator);
1834 newIndex = children.indexOf(node);
1837 if (oldIndex != newIndex) {
1838 List<Object> pathsToExpand = new ArrayList<Object>();
1839 List<Object> selectionPaths = new ArrayList<Object>();
1840 TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
1841 removeNodeFromParent(node, false);
1842 myTreeModel.insertNodeInto(node, parentNode, newIndex);
1843 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
1845 else {
1846 myTreeModel.nodeChanged(node);
1849 else {
1850 myTreeModel.nodeChanged(node);
1854 public DefaultTreeModel getTreeModel() {
1855 return myTreeModel;
1858 private void insertNodesInto(ArrayList<TreeNode> nodes, DefaultMutableTreeNode parentNode) {
1859 if (nodes.isEmpty()) return;
1861 nodes = new ArrayList<TreeNode>(nodes);
1862 Collections.sort(nodes, myNodeComparator);
1864 ArrayList<TreeNode> all = TreeUtil.childrenToArray(parentNode);
1865 all.addAll(nodes);
1866 Collections.sort(all, myNodeComparator);
1868 int[] indices = new int[nodes.size()];
1869 int idx = 0;
1870 for (int i = 0; i < nodes.size(); i++) {
1871 TreeNode node = nodes.get(i);
1872 while (all.get(idx) != node) idx++;
1873 indices[i] = idx;
1874 parentNode.insert((MutableTreeNode)node, idx);
1877 myTreeModel.nodesWereInserted(parentNode, indices);
1880 private void disposeNode(DefaultMutableTreeNode node) {
1881 myUpdatingChildren.remove(node);
1882 removeFromUnbuilt(node);
1884 if (node.getChildCount() > 0) {
1885 for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
1886 disposeNode(_node);
1889 if (isLoadingNode(node)) return;
1890 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1891 if (descriptor == null) return;
1892 final Object element = getElementFromDescriptor(descriptor);
1893 removeMapping(element, node, null);
1894 node.setUserObject(null);
1895 node.removeAllChildren();
1898 public void addSubtreeToUpdate(final DefaultMutableTreeNode root) {
1899 addSubtreeToUpdate(root, null);
1902 public void addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
1903 getUpdater().runAfterUpdate(runAfterUpdate);
1904 getUpdater().addSubtreeToUpdate(root);
1907 public boolean wasRootNodeInitialized() {
1908 return myRootNodeWasInitialized;
1911 private boolean isRootNodeBuilt() {
1912 return myRootNodeWasInitialized && isNodeBeingBuilt(myRootNode);
1915 public void select(final Object[] elements, @Nullable final Runnable onDone) {
1916 select(elements, onDone, false);
1919 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
1920 select(elements, onDone, addToSelection, false);
1923 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) {
1924 _select(elements, onDone, addToSelection, true, false, deferred);
1927 void _select(final Object[] elements,
1928 final Runnable onDone,
1929 final boolean addToSelection,
1930 final boolean checkCurrentSelection,
1931 final boolean checkIfInStructure) {
1933 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, false);
1936 void _select(final Object[] elements,
1937 final Runnable onDone,
1938 final boolean addToSelection,
1939 final boolean checkCurrentSelection,
1940 final boolean checkIfInStructure,
1941 final boolean deferred) {
1943 boolean willAffectSelection = elements.length > 0 || (elements.length == 0 && addToSelection);
1944 if (!willAffectSelection) {
1945 runDone(onDone);
1946 return;
1949 final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections;
1951 if (!deferred && wasRootNodeInitialized() && willAffectSelection) {
1952 myCanProcessDeferredSelections = false;
1955 if (!checkDeferred(deferred, onDone)) return;
1957 if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) {
1958 getTree().clearSelection();
1962 runDone(new Runnable() {
1963 public void run() {
1964 if (!checkDeferred(deferred, onDone)) return;
1966 final Set<Object> currentElements = getSelectedElements();
1968 if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
1969 boolean runSelection = false;
1970 for (Object eachToSelect : elements) {
1971 if (!currentElements.contains(eachToSelect)) {
1972 runSelection = true;
1973 break;
1977 if (!runSelection) {
1978 if (elements.length > 0) {
1979 selectVisible(elements[0], onDone, true, true);
1981 return;
1985 Set<Object> toSelect = new HashSet<Object>();
1986 myTree.clearSelection();
1987 toSelect.addAll(Arrays.asList(elements));
1988 if (addToSelection) {
1989 toSelect.addAll(currentElements);
1992 if (checkIfInStructure) {
1993 final Iterator<Object> allToSelect = toSelect.iterator();
1994 while (allToSelect.hasNext()) {
1995 Object each = allToSelect.next();
1996 if (!isInStructure(each)) {
1997 allToSelect.remove();
2002 final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
2004 if (wasRootNodeInitialized()) {
2005 final int[] originalRows = myTree.getSelectionRows();
2006 if (!addToSelection) {
2007 myTree.clearSelection();
2009 addNext(elementsToSelect, 0, onDone, originalRows, deferred);
2011 else {
2012 addToDeferred(elementsToSelect, onDone);
2018 private void addToDeferred(final Object[] elementsToSelect, final Runnable onDone) {
2019 myDeferredSelections.clear();
2020 myDeferredSelections.add(new Runnable() {
2021 public void run() {
2022 select(elementsToSelect, onDone, false, true);
2027 private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) {
2028 if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) {
2029 return true;
2030 } else {
2031 runDone(onDone);
2032 return false;
2036 @NotNull
2037 final Set<Object> getSelectedElements() {
2038 final TreePath[] paths = myTree.getSelectionPaths();
2040 Set<Object> result = new HashSet<Object>();
2041 if (paths != null) {
2042 for (TreePath eachPath : paths) {
2043 if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2044 final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
2045 final Object eachElement = getElementFor(eachNode);
2046 if (eachElement != null) {
2047 result.add(eachElement);
2052 return result;
2056 private void addNext(final Object[] elements, final int i, @Nullable final Runnable onDone, final int[] originalRows, final boolean deferred) {
2057 if (i >= elements.length) {
2058 if (myTree.isSelectionEmpty()) {
2059 myTree.setSelectionRows(originalRows);
2061 runDone(onDone);
2063 else {
2064 if (!checkDeferred(deferred, onDone)) {
2065 return;
2068 doSelect(elements[i], new Runnable() {
2069 public void run() {
2070 if (!checkDeferred(deferred, onDone)) return;
2072 addNext(elements, i + 1, onDone, originalRows, deferred);
2074 }, true, deferred, i == 0);
2078 public void select(final Object element, @Nullable final Runnable onDone) {
2079 select(element, onDone, false);
2082 public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
2083 _select(new Object[] {element}, onDone, addToSelection, true, false);
2086 private void doSelect(final Object element, final Runnable onDone, final boolean addToSelection, final boolean deferred, final boolean canBeCentered) {
2087 final Runnable _onDone = new Runnable() {
2088 public void run() {
2089 if (!checkDeferred(deferred, onDone)) return;
2090 selectVisible(element, onDone, addToSelection, canBeCentered);
2093 _expand(element, _onDone, true, false);
2096 private void selectVisible(Object element, final Runnable onDone, boolean addToSelection, boolean canBeCentered) {
2097 final DefaultMutableTreeNode toSelect = getNodeForElement(element, false);
2098 if (toSelect == null) {
2099 runDone(onDone);
2100 return;
2102 final int row = myTree.getRowForPath(new TreePath(toSelect.getPath()));
2104 if (myUpdaterState != null) {
2105 myUpdaterState.addSelection(element);
2108 if (Registry.is("ide.tree.autoscrollToVCenter") && canBeCentered) {
2109 runDone(new Runnable() {
2110 public void run() {
2111 TreeUtil.showRowCentered(myTree, row, false).doWhenDone(new Runnable() {
2112 public void run() {
2113 runDone(onDone);
2118 } else {
2119 TreeUtil.showAndSelect(myTree, row - 2, row + 2, row, -1, addToSelection).doWhenDone(new Runnable() {
2120 public void run() {
2121 runDone(onDone);
2127 public void expand(final Object element, @Nullable final Runnable onDone) {
2128 expand(element, onDone, false);
2132 void expand(final Object element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
2133 _expand(new Object[]{element}, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure);
2136 void expand(final Object[] element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
2137 _expand(element, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure);
2140 void _expand(final Object[] element, @NotNull final Runnable onDone, final boolean parentsOnly, final boolean checkIfInStructure) {
2141 runDone(new Runnable() {
2142 public void run() {
2143 if (element.length == 0) {
2144 runDone(onDone);
2145 return;
2148 if (myUpdaterState != null) {
2149 myUpdaterState.clearExpansion();
2153 final ActionCallback done = new ActionCallback(element.length);
2154 done.doWhenDone(new Runnable() {
2155 public void run() {
2156 runDone(onDone);
2160 for (final Object toExpand : element) {
2161 _expand(toExpand, new Runnable() {
2162 public void run() {
2163 done.setDone();
2165 }, parentsOnly, checkIfInStructure);
2171 public void collapseChildren(final Object element, @Nullable final Runnable onDone) {
2172 runDone(new Runnable() {
2173 public void run() {
2174 final DefaultMutableTreeNode node = getNodeForElement(element, false);
2175 if (node != null) {
2176 getTree().collapsePath(new TreePath(node.getPath()));
2177 runDone(onDone);
2183 private void runDone(@Nullable Runnable done) {
2184 if (isReleased()) return;
2185 if (done == null) return;
2187 if (isYeildingNow()) {
2188 if (!myYeildingDoneRunnables.contains(done)) {
2189 myYeildingDoneRunnables.add(done);
2192 else {
2193 done.run();
2197 private void _expand(final Object element, @NotNull final Runnable onDone, final boolean parentsOnly, boolean checkIfInStructure) {
2198 if (checkIfInStructure && !isInStructure(element)) {
2199 runDone(onDone);
2200 return;
2203 if (wasRootNodeInitialized()) {
2204 List<Object> kidsToExpand = new ArrayList<Object>();
2205 Object eachElement = element;
2206 DefaultMutableTreeNode firstVisible = null;
2207 while (true) {
2208 if (!isValid(eachElement)) break;
2210 firstVisible = getNodeForElement(eachElement, true);
2211 if (eachElement != element || !parentsOnly) {
2212 assert !kidsToExpand.contains(eachElement) :
2213 "Not a valid tree structure, walking up the structure gives many entries for element=" +
2214 eachElement +
2215 ", root=" +
2216 getTreeStructure().getRootElement();
2217 kidsToExpand.add(eachElement);
2219 if (firstVisible != null) break;
2220 eachElement = getTreeStructure().getParentElement(eachElement);
2221 if (eachElement == null) {
2222 firstVisible = null;
2223 break;
2227 if (firstVisible == null) {
2228 runDone(onDone);
2230 else if (kidsToExpand.size() == 0) {
2231 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)firstVisible.getParent();
2232 if (parentNode != null) {
2233 final TreePath parentPath = new TreePath(parentNode.getPath());
2234 if (!myTree.isExpanded(parentPath)) {
2235 expand(parentPath);
2238 runDone(onDone);
2240 else {
2241 processExpand(firstVisible, kidsToExpand, kidsToExpand.size() - 1, onDone);
2244 else {
2245 deferExpansion(element, onDone, parentsOnly);
2249 private void deferExpansion(final Object element, final Runnable onDone, final boolean parentsOnly) {
2250 myDeferredExpansions.add(new Runnable() {
2251 public void run() {
2252 _expand(element, onDone, parentsOnly, false);
2257 private void processExpand(final DefaultMutableTreeNode toExpand,
2258 final List kidsToExpand,
2259 final int expandIndex,
2260 @NotNull final Runnable onDone) {
2261 final Object element = getElementFor(toExpand);
2262 if (element == null) {
2263 runDone(onDone);
2264 return;
2267 addNodeAction(element, new NodeAction() {
2268 public void onReady(final DefaultMutableTreeNode node) {
2269 if (node.getChildCount() >= 0 && !myTree.isExpanded(new TreePath(node.getPath()))) {
2270 final ArrayList list = new ArrayList();
2271 for (int i = 0; i < node.getChildCount(); i++) {
2272 list.add(node.getChildAt(i));
2274 expand(node);
2277 if (expandIndex < 0) {
2278 runDone(onDone);
2279 return;
2282 final DefaultMutableTreeNode nextNode = getNodeForElement(kidsToExpand.get(expandIndex), false);
2283 if (nextNode != null) {
2284 processExpand(nextNode, kidsToExpand, expandIndex - 1, onDone);
2286 else {
2287 runDone(onDone);
2292 expand(toExpand);
2296 @Nullable
2297 private Object getElementFor(Object node) {
2298 if (!(node instanceof DefaultMutableTreeNode)) return null;
2299 return getElementFor((DefaultMutableTreeNode)node);
2302 @Nullable
2303 private Object getElementFor(DefaultMutableTreeNode node) {
2304 if (node != null) {
2305 final Object o = node.getUserObject();
2306 if (o instanceof NodeDescriptor) {
2307 return getElementFromDescriptor(((NodeDescriptor)o));
2311 return null;
2314 public final boolean isNodeBeingBuilt(final TreePath path) {
2315 return isNodeBeingBuilt(path.getLastPathComponent());
2318 public final boolean isNodeBeingBuilt(Object node) {
2319 if (isParentLoading(node) || isLoadingParent(node)) return true;
2321 final boolean childrenAreNoLoadedYet = isLoadingChildrenFor(node) && myUnbuiltNodes.contains(node);
2322 if (childrenAreNoLoadedYet) {
2323 if (node instanceof DefaultMutableTreeNode) {
2324 final TreePath nodePath = new TreePath(((DefaultMutableTreeNode)node).getPath());
2325 if (!myTree.isExpanded(nodePath)) return false;
2328 return true;
2332 return false;
2335 private boolean isLoadingParent(Object node) {
2336 if (!(node instanceof DefaultMutableTreeNode)) return false;
2337 return isLoadedInBackground(getElementFor((DefaultMutableTreeNode)node));
2340 public void setTreeStructure(final AbstractTreeStructure treeStructure) {
2341 myTreeStructure = treeStructure;
2342 clearUpdaterState();
2345 public AbstractTreeUpdater getUpdater() {
2346 return myUpdater;
2349 public void setUpdater(final AbstractTreeUpdater updater) {
2350 myUpdater = updater;
2353 public DefaultMutableTreeNode getRootNode() {
2354 return myRootNode;
2357 public void setRootNode(@NotNull final DefaultMutableTreeNode rootNode) {
2358 myRootNode = rootNode;
2361 private void dropUpdaterStateIfExternalChange() {
2362 if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) {
2363 clearUpdaterState();
2367 private void clearUpdaterState() {
2368 myUpdaterState = null;
2371 private void createMapping(Object element, DefaultMutableTreeNode node) {
2372 if (!myElementToNodeMap.containsKey(element)) {
2373 myElementToNodeMap.put(element, node);
2375 else {
2376 final Object value = myElementToNodeMap.get(element);
2377 final List<DefaultMutableTreeNode> nodes;
2378 if (value instanceof DefaultMutableTreeNode) {
2379 nodes = new ArrayList<DefaultMutableTreeNode>();
2380 nodes.add((DefaultMutableTreeNode)value);
2381 myElementToNodeMap.put(element, nodes);
2383 else {
2384 nodes = (List<DefaultMutableTreeNode>)value;
2386 nodes.add(node);
2390 private void removeMapping(Object element, DefaultMutableTreeNode node, @Nullable Object elementToPutNodeActionsFor) {
2391 final Object value = myElementToNodeMap.get(element);
2392 if (value != null) {
2393 if (value instanceof DefaultMutableTreeNode) {
2394 if (value.equals(node)) {
2395 myElementToNodeMap.remove(element);
2398 else {
2399 List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
2400 final boolean reallyRemoved = nodes.remove(node);
2401 if (reallyRemoved) {
2402 if (nodes.isEmpty()) {
2403 myElementToNodeMap.remove(element);
2409 final List<NodeAction> actions = myNodeActions.get(element);
2410 myNodeActions.remove(element);
2412 if (elementToPutNodeActionsFor != null && actions != null) {
2413 myNodeActions.put(elementToPutNodeActionsFor, actions);
2417 private DefaultMutableTreeNode getFirstNode(Object element) {
2418 return findNode(element, 0);
2421 private DefaultMutableTreeNode findNode(final Object element, int startIndex) {
2422 final Object value = getBuilder().findNodeByElement(element);
2423 if (value == null) {
2424 return null;
2426 if (value instanceof DefaultMutableTreeNode) {
2427 return startIndex == 0 ? (DefaultMutableTreeNode)value : null;
2429 final List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
2430 return startIndex < nodes.size() ? nodes.get(startIndex) : null;
2433 protected Object findNodeByElement(Object element) {
2434 if (myElementToNodeMap.containsKey(element)) {
2435 return myElementToNodeMap.get(element);
2438 try {
2439 TREE_NODE_WRAPPER.setValue(element);
2440 return myElementToNodeMap.get(TREE_NODE_WRAPPER);
2442 finally {
2443 TREE_NODE_WRAPPER.setValue(null);
2447 private DefaultMutableTreeNode findNodeForChildElement(DefaultMutableTreeNode parentNode, Object element) {
2448 final Object value = myElementToNodeMap.get(element);
2449 if (value == null) {
2450 return null;
2453 if (value instanceof DefaultMutableTreeNode) {
2454 final DefaultMutableTreeNode elementNode = (DefaultMutableTreeNode)value;
2455 return parentNode.equals(elementNode.getParent()) ? elementNode : null;
2458 final List<DefaultMutableTreeNode> allNodesForElement = (List<DefaultMutableTreeNode>)value;
2459 for (final DefaultMutableTreeNode elementNode : allNodesForElement) {
2460 if (parentNode.equals(elementNode.getParent())) {
2461 return elementNode;
2465 return null;
2468 public void cancelBackgroundLoading() {
2469 if (myWorker != null) {
2470 myWorker.cancelTasks();
2471 clearWorkerTasks();
2473 myNodeActions.clear();
2476 private void addNodeAction(Object element, NodeAction action) {
2477 maybeSetBusyAndScheduleWaiterForReady(true);
2479 List<NodeAction> list = myNodeActions.get(element);
2480 if (list == null) {
2481 list = new ArrayList<NodeAction>();
2482 myNodeActions.put(element, list);
2484 list.add(action);
2487 private void cleanUpNow() {
2488 if (isReleased()) return;
2490 final UpdaterTreeState state = new UpdaterTreeState(this);
2492 myTree.collapsePath(new TreePath(myTree.getModel().getRoot()));
2493 myTree.clearSelection();
2494 getRootNode().removeAllChildren();
2496 myRootNodeWasInitialized = false;
2497 myNodeActions.clear();
2498 myElementToNodeMap.clear();
2499 myDeferredSelections.clear();
2500 myDeferredExpansions.clear();
2501 myLoadingParents.clear();
2502 myUnbuiltNodes.clear();
2503 myUpdateFromRootRequested = true;
2505 if (myWorker != null) {
2506 Disposer.dispose(myWorker);
2507 myWorker = null;
2510 myTree.invalidate();
2512 state.restore();
2515 public AbstractTreeUi setClearOnHideDelay(final long clearOnHideDelay) {
2516 myClearOnHideDelay = clearOnHideDelay;
2517 return this;
2520 public void setJantorPollPeriod(final long time) {
2521 myJanitorPollPeriod = time;
2524 public void setCheckStructure(final boolean checkStructure) {
2525 myCheckStructure = checkStructure;
2528 private class MySelectionListener implements TreeSelectionListener {
2529 public void valueChanged(final TreeSelectionEvent e) {
2530 dropUpdaterStateIfExternalChange();
2534 private class MyExpansionListener implements TreeExpansionListener {
2535 public void treeExpanded(TreeExpansionEvent event) {
2536 dropUpdaterStateIfExternalChange();
2538 TreePath path = event.getPath();
2539 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2541 if (!myUnbuiltNodes.contains(node)) {
2542 return;
2544 removeFromUnbuilt(node);
2547 getBuilder().expandNodeChildren(node);
2549 runDone(new Runnable() {
2550 public void run() {
2551 final Object element = getElementFor(node);
2553 removeLoading(node, false);
2555 if (node.getChildCount() == 0) {
2556 addNodeAction(element, new NodeAction() {
2557 public void onReady(final DefaultMutableTreeNode node) {
2558 expand(element, null);
2563 processSmartExpand(node);
2568 public void treeCollapsed(TreeExpansionEvent e) {
2569 final TreePath path = e.getPath();
2570 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2571 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2573 TreePath pathToSelect = null;
2574 if (isSelectionInside(node)) {
2575 pathToSelect = new TreePath(node.getPath());
2579 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
2580 if (getBuilder().isDisposeOnCollapsing(descriptor)) {
2581 runDone(new Runnable() {
2582 public void run() {
2583 if (isDisposed(node)) return;
2585 TreePath nodePath = new TreePath(node.getPath());
2586 if (myTree.isExpanded(nodePath)) return;
2588 removeChildren(node);
2589 addLoadingNode(node);
2592 if (node.equals(getRootNode())) {
2593 addSelectionPath(new TreePath(getRootNode().getPath()), true, Condition.FALSE);
2595 else {
2596 myTreeModel.reload(node);
2600 if (pathToSelect != null && myTree.isSelectionEmpty()) {
2601 addSelectionPath(pathToSelect, true, Condition.FALSE);
2605 private void removeChildren(DefaultMutableTreeNode node) {
2606 EnumerationCopy copy = new EnumerationCopy(node.children());
2607 while (copy.hasMoreElements()) {
2608 disposeNode((DefaultMutableTreeNode)copy.nextElement());
2610 node.removeAllChildren();
2611 myTreeModel.nodeStructureChanged(node);
2614 private boolean isSelectionInside(DefaultMutableTreeNode parent) {
2615 TreePath path = new TreePath(myTreeModel.getPathToRoot(parent));
2616 TreePath[] paths = myTree.getSelectionPaths();
2617 if (paths == null) return false;
2618 for (TreePath path1 : paths) {
2619 if (path.isDescendant(path1)) return true;
2621 return false;
2625 public boolean isInStructure(@Nullable Object element) {
2626 Object eachParent = element;
2627 while (eachParent != null) {
2628 if (getTreeStructure().getRootElement().equals(eachParent)) return true;
2629 eachParent = getTreeStructure().getParentElement(eachParent);
2632 return false;
2635 interface NodeAction {
2636 void onReady(DefaultMutableTreeNode node);
2639 public void setCanYield(final boolean canYield) {
2640 myCanYield = canYield;
2643 public Collection<TreeUpdatePass> getYeildingPasses() {
2644 return myYeildingPasses;
2647 public boolean isBuilt(Object element) {
2648 if (!myElementToNodeMap.containsKey(element)) return false;
2649 final Object node = myElementToNodeMap.get(element);
2650 return !myUnbuiltNodes.contains(node);