TreeUi: memory leaks fixed when TreeStructure produces new objects
[fedora-idea.git] / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUi.java
blob6c927c367bee8d2257425522f7072a3c1362010c
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.application.ModalityState;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.progress.ProcessCanceledException;
9 import com.intellij.openapi.progress.ProgressIndicator;
10 import com.intellij.openapi.progress.ProgressManager;
11 import com.intellij.openapi.project.IndexNotReadyException;
12 import com.intellij.openapi.util.*;
13 import com.intellij.openapi.util.registry.Registry;
14 import com.intellij.openapi.util.registry.RegistryValue;
15 import com.intellij.ui.LoadingNode;
16 import com.intellij.ui.treeStructure.Tree;
17 import com.intellij.util.Alarm;
18 import com.intellij.util.ArrayUtil;
19 import com.intellij.util.ConcurrencyUtil;
20 import com.intellij.util.Time;
21 import com.intellij.util.concurrency.WorkerThread;
22 import com.intellij.util.containers.HashSet;
23 import com.intellij.util.enumeration.EnumerationCopy;
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.security.AccessControlException;
38 import java.util.*;
39 import java.util.List;
40 import java.util.concurrent.ScheduledExecutorService;
41 import java.util.concurrent.TimeUnit;
43 class AbstractTreeUi {
44 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
45 protected JTree myTree;// protected for TestNG
46 @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel;
47 private AbstractTreeStructure myTreeStructure;
48 private AbstractTreeUpdater myUpdater;
49 private Comparator<NodeDescriptor> myNodeDescriptorComparator;
50 private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() {
51 public int compare(TreeNode n1, TreeNode n2) {
52 if (isLoadingNode(n1) || isLoadingNode(n2)) return 0;
53 NodeDescriptor nodeDescriptor1 = (NodeDescriptor)((DefaultMutableTreeNode)n1).getUserObject();
54 NodeDescriptor nodeDescriptor2 = (NodeDescriptor)((DefaultMutableTreeNode)n2).getUserObject();
55 return myNodeDescriptorComparator != null
56 ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2)
57 : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex();
60 private DefaultMutableTreeNode myRootNode;
61 private final HashMap<Object, Object> myElementToNodeMap = new HashMap<Object, Object>();
62 private final HashSet<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>();
63 private TreeExpansionListener myExpansionListener;
64 private MySelectionListener mySelectionListener;
66 private WorkerThread myWorker = null;
67 private final ArrayList<Runnable> myActiveWorkerTasks = new ArrayList<Runnable>();
69 private ProgressIndicator myProgress;
70 private static final int WAIT_CURSOR_DELAY = 100;
71 private AbstractTreeNode<Object> TREE_NODE_WRAPPER;
72 private boolean myRootNodeWasInitialized = false;
73 private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>();
74 private boolean myUpdateFromRootRequested;
75 private boolean myWasEverShown;
76 private boolean myUpdateIfInactive;
77 private final List<Object> myLoadingParents = new ArrayList<Object>();
78 private long myClearOnHideDelay = -1;
79 private ScheduledExecutorService ourClearanceService;
80 private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
82 private final List<Runnable> myDeferredSelections = new ArrayList<Runnable>();
83 private final List<Runnable> myDeferredExpansions = new ArrayList<Runnable>();
85 private boolean myCanProcessDeferredSelections;
87 private UpdaterTreeState myUpdaterState;
88 private AbstractTreeBuilder myBuilder;
90 private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
91 private long myJanitorPollPeriod = Time.SECOND * 10;
92 private boolean myCheckStructure = false;
95 private boolean myCanYield = false;
97 private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
99 private boolean myYeildingNow;
101 private List<DefaultMutableTreeNode> myPendingNodeActions = new ArrayList<DefaultMutableTreeNode>();
102 private List<Runnable> myYeildingDoneRunnables = new ArrayList<Runnable>();
104 private Alarm myBusyAlarm = new Alarm();
105 private Runnable myWaiterForReady = new Runnable() {
106 public void run() {
107 maybeSetBusyAndScheduleWaiterForReady(false);
111 private RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
112 private RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
113 private RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimout");
115 private boolean myWasEverIndexNotReady;
117 protected final void init(AbstractTreeBuilder builder,
118 JTree tree,
119 DefaultTreeModel treeModel,
120 AbstractTreeStructure treeStructure,
121 Comparator<NodeDescriptor> comparator) {
123 init(builder, tree, treeModel, treeStructure, comparator, true);
126 protected void init(AbstractTreeBuilder builder,
127 JTree tree,
128 DefaultTreeModel treeModel,
129 AbstractTreeStructure treeStructure,
130 Comparator<NodeDescriptor> comparator,
131 boolean updateIfInactive) {
132 myBuilder = builder;
133 myTree = tree;
134 myTreeModel = treeModel;
135 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
136 myTree.setModel(myTreeModel);
137 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
138 setTreeStructure(treeStructure);
139 myNodeDescriptorComparator = comparator;
140 myUpdateIfInactive = updateIfInactive;
142 myExpansionListener = new MyExpansionListener();
143 myTree.addTreeExpansionListener(myExpansionListener);
145 mySelectionListener = new MySelectionListener();
146 myTree.addTreeSelectionListener(mySelectionListener);
148 setUpdater(getBuilder().createUpdater());
149 myProgress = getBuilder().createProgressIndicator();
150 Disposer.register(getBuilder(), getUpdater());
152 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
153 public void showNotify() {
154 if (!isReleased()) {
155 AbstractTreeUi.this.showNotify();
159 public void hideNotify() {
160 if (!isReleased()) {
161 AbstractTreeUi.this.hideNotify();
165 Disposer.register(getBuilder(), uiNotify);
168 protected void hideNotify() {
169 myBusyAlarm.cancelAllRequests();
171 if (!myWasEverShown) return;
173 if (!myNodeActions.isEmpty()) {
174 cancelBackgroundLoading();
175 myUpdateFromRootRequested = true;
178 if (getClearOnHideDelay() >= 0) {
179 ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
180 initClearanceServiceIfNeeded();
184 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
185 if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
187 if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
188 final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
189 final boolean isBusy = !isReady() || forcedBusy;
190 if (isBusy && tree.isShowing()) {
191 tree.setPaintBusy(true);
192 myBusyAlarm.cancelAllRequests();
193 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
195 else {
196 tree.setPaintBusy(false);
201 private void initClearanceServiceIfNeeded() {
202 if (ourClearanceService != null) return;
204 ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor");
205 ourClearanceService.scheduleWithFixedDelay(new Runnable() {
206 public void run() {
207 cleanUpAll();
209 }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
212 private void cleanUpAll() {
213 final long now = System.currentTimeMillis();
214 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
215 for (AbstractTreeUi eachUi : uis) {
216 if (eachUi == null) continue;
217 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
218 if (timeToCleanup == null) continue;
219 if (now >= timeToCleanup.longValue()) {
220 ourUi2Countdown.remove(eachUi);
221 getBuilder().cleanUp();
226 protected void doCleanUp() {
227 final Application app = ApplicationManager.getApplication();
228 if (app != null && app.isUnitTestMode()) {
229 cleanUpNow();
231 else {
232 // we are not in EDT
233 //noinspection SSBasedInspection
234 SwingUtilities.invokeLater(new Runnable() {
235 public void run() {
236 if (!isReleased()) {
237 cleanUpNow();
244 private void disposeClearanceService() {
245 try {
246 if (ourClearanceService != null) {
247 ourClearanceService.shutdown();
248 ourClearanceService = null;
251 catch (AccessControlException e) {
252 LOG.warn(e);
256 void showNotify() {
257 myCanProcessDeferredSelections = true;
259 ourUi2Countdown.remove(this);
261 if (!myWasEverShown || myUpdateFromRootRequested) {
262 if (wasRootNodeInitialized()) {
263 getBuilder().updateFromRoot();
265 else {
266 initRootNodeNowIfNeeded(new TreeUpdatePass(getRootNode()));
267 getBuilder().updateFromRoot();
270 myWasEverShown = true;
273 public void release() {
274 if (isReleased()) return;
276 myTree.removeTreeExpansionListener(myExpansionListener);
277 myTree.removeTreeSelectionListener(mySelectionListener);
278 disposeNode(getRootNode());
279 myElementToNodeMap.clear();
280 getUpdater().cancelAllRequests();
281 if (myWorker != null) {
282 myWorker.dispose(true);
283 clearWorkerTasks();
285 TREE_NODE_WRAPPER.setValue(null);
286 if (myProgress != null) {
287 myProgress.cancel();
289 disposeClearanceService();
291 myTree = null;
292 setUpdater(null);
293 myWorker = null;
294 //todo [kirillk] afraid to do so just in release day, to uncomment
295 // myTreeStructure = null;
296 myBuilder = null;
299 public boolean isReleased() {
300 return myBuilder == null;
303 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
304 getTreeStructure().commit();
305 addNodeAction(getElementFor(node), new NodeAction() {
306 public void onReady(final DefaultMutableTreeNode node) {
307 processSmartExpand(node);
310 getUpdater().addSubtreeToUpdate(node);
311 getUpdater().performUpdate();
314 public final AbstractTreeStructure getTreeStructure() {
315 return myTreeStructure;
318 public final JTree getTree() {
319 return myTree;
322 @Nullable
323 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
324 DefaultMutableTreeNode result = null;
325 if (validateAgainstStructure) {
326 int index = 0;
327 while (true) {
328 final DefaultMutableTreeNode node = findNode(element, index);
329 if (node == null) break;
331 if (isNodeValidForElement(element, node)) {
332 result = node;
333 break;
336 index++;
339 else {
340 result = getFirstNode(element);
344 if (result != null && !isNodeInStructure(result)) {
345 disposeNode(result);
346 result = null;
349 return result;
352 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
353 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
356 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
357 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
360 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
361 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
362 final Object parentElement = getElementFor(parent);
363 if (!isInStructure(parentElement)) return false;
365 if (parent instanceof ElementNode) {
366 return ((ElementNode)parent).isValidChild(element);
368 else {
369 for (int i = 0; i < parent.getChildCount(); i++) {
370 final TreeNode child = parent.getChildAt(i);
371 final Object eachElement = getElementFor(child);
372 if (element.equals(eachElement)) return true;
376 return false;
379 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
380 boolean valid = true;
381 while (true) {
382 if (eachParent == null) {
383 valid = eachParentNode == null;
384 break;
387 if (!eachParent.equals(getElementFor(eachParentNode))) {
388 valid = false;
389 break;
392 eachParent = getTreeStructure().getParentElement(eachParent);
393 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
395 return valid;
398 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
399 DefaultMutableTreeNode node = null;
400 for (final Object pathElement : path) {
401 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
402 if (node == null) {
403 break;
406 return node;
409 public final void buildNodeForElement(Object element) {
410 getUpdater().performUpdate();
411 DefaultMutableTreeNode node = getNodeForElement(element, false);
412 if (node == null) {
413 final java.util.List<Object> elements = new ArrayList<Object>();
414 while (true) {
415 element = getTreeStructure().getParentElement(element);
416 if (element == null) {
417 break;
419 elements.add(0, element);
422 for (final Object element1 : elements) {
423 node = getNodeForElement(element1, false);
424 if (node != null) {
425 expand(node);
431 public final void buildNodeForPath(Object[] path) {
432 getUpdater().performUpdate();
433 DefaultMutableTreeNode node = null;
434 for (final Object pathElement : path) {
435 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
436 if (node != null && node != path[path.length - 1]) {
437 expand(node);
442 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
443 myNodeDescriptorComparator = nodeDescriptorComparator;
444 List<Object> pathsToExpand = new ArrayList<Object>();
445 List<Object> selectionPaths = new ArrayList<Object>();
446 TreeBuilderUtil.storePaths(getBuilder(), getRootNode(), pathsToExpand, selectionPaths, false);
447 resortChildren(getRootNode());
448 myTreeModel.nodeStructureChanged(getRootNode());
449 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
452 protected AbstractTreeBuilder getBuilder() {
453 return myBuilder;
456 private void resortChildren(DefaultMutableTreeNode node) {
457 ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
458 node.removeAllChildren();
459 Collections.sort(childNodes, myNodeComparator);
460 for (TreeNode childNode1 : childNodes) {
461 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)childNode1;
462 node.add(childNode);
463 resortChildren(childNode);
467 protected final void initRootNode() {
468 final Activatable activatable = new Activatable() {
469 public void showNotify() {
470 if (!myRootNodeWasInitialized) {
471 initRootNodeNowIfNeeded(new TreeUpdatePass(getRootNode()));
475 public void hideNotify() {
479 if (myUpdateIfInactive || ApplicationManager.getApplication().isUnitTestMode()) {
480 activatable.showNotify();
482 else {
483 new UiNotifyConnector.Once(myTree, activatable);
487 private void initRootNodeNowIfNeeded(TreeUpdatePass pass) {
488 if (myRootNodeWasInitialized) return;
490 myRootNodeWasInitialized = true;
491 Object rootElement = getTreeStructure().getRootElement();
492 addNodeAction(rootElement, new NodeAction() {
493 public void onReady(final DefaultMutableTreeNode node) {
494 processDeferredActions();
497 NodeDescriptor nodeDescriptor = getTreeStructure().createDescriptor(rootElement, null);
498 getRootNode().setUserObject(nodeDescriptor);
499 update(nodeDescriptor, false);
500 if (getElementFromDescriptor(nodeDescriptor) != null) {
501 createMapping(getElementFromDescriptor(nodeDescriptor), getRootNode());
503 addLoadingNode(getRootNode());
504 boolean willUpdate = false;
505 if (getBuilder().isAutoExpandNode(nodeDescriptor)) {
506 willUpdate = myUnbuiltNodes.contains(getRootNode());
507 expand(getRootNode());
509 if (!willUpdate) {
510 updateNodeChildren(getRootNode(), pass, null, false);
512 if (getRootNode().getChildCount() == 0) {
513 myTreeModel.nodeChanged(getRootNode());
516 if (!myLoadingParents.contains(getTreeStructure().getRootElement())) {
517 processDeferredActions();
521 private boolean update(final NodeDescriptor nodeDescriptor, boolean canBeNonEdt) {
522 if (!canBeNonEdt && myWasEverShown) {
523 assertIsDispatchThread();
526 final Application app = ApplicationManager.getApplication();
527 if (app.isDispatchThread() || !myWasEverShown) {
528 return getBuilder().updateNodeDescriptor(nodeDescriptor);
530 else {
531 app.invokeLater(new Runnable() {
532 public void run() {
533 if (!isReleased()) {
534 getBuilder().updateNodeDescriptor(nodeDescriptor);
537 }, ModalityState.stateForComponent(myTree));
538 return true;
542 private void assertIsDispatchThread() {
543 if (myTree.isShowing()) {
544 ApplicationManager.getApplication().assertIsDispatchThread();
548 private void processDeferredActions() {
549 processDeferredActions(myDeferredSelections);
550 processDeferredActions(myDeferredExpansions);
553 private void processDeferredActions(List<Runnable> actions) {
554 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
555 actions.clear();
556 for (Runnable runnable : runnables) {
557 runnable.run();
561 public void doUpdateFromRoot() {
562 updateSubtree(getRootNode());
565 public ActionCallback doUpdateFromRootCB() {
566 final ActionCallback cb = new ActionCallback();
567 getUpdater().runAfterUpdate(new Runnable() {
568 public void run() {
569 cb.setDone();
572 updateSubtree(getRootNode());
573 return cb;
576 public final void updateSubtree(DefaultMutableTreeNode node) {
577 updateSubtree(new TreeUpdatePass(node));
580 public final void updateSubtree(TreeUpdatePass pass) {
581 maybeSetBusyAndScheduleWaiterForReady(true);
583 initRootNodeNowIfNeeded(pass);
585 final DefaultMutableTreeNode node = pass.getNode();
587 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
589 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
591 getBuilder().updateNode(node);
592 updateNodeChildren(node, pass, null, false);
595 @NotNull
596 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
597 final UpdaterTreeState oldState = myUpdaterState;
598 if (oldState == null) {
599 myUpdaterState = state;
600 return state;
602 else {
603 oldState.addAll(state);
604 return oldState;
608 protected void doUpdateNode(DefaultMutableTreeNode node) {
609 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
610 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
611 Object prevElement = getElementFromDescriptor(descriptor);
612 if (prevElement == null) return;
613 boolean changes = update(descriptor, false);
614 if (getElementFromDescriptor(descriptor) == null) {
615 LOG.assertTrue(false, "element == null, updateSubtree should be invoked for parent! builder=" +
616 getBuilder() +
617 ", prevElement = " +
618 prevElement +
619 ", node = " +
620 node +
621 "; parentDescriptor=" +
622 descriptor.getParentDescriptor());
624 if (changes) {
625 updateNodeImageAndPosition(node);
629 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
630 return getBuilder().getTreeStructureElement(descriptor);
633 private void updateNodeChildren(final DefaultMutableTreeNode node,
634 final TreeUpdatePass pass,
635 @Nullable Object[] preloadedChildren,
636 boolean forcedNow) {
637 getTreeStructure().commit();
638 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath()));
639 final boolean wasLeaf = node.getChildCount() == 0;
641 final NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
643 if (descriptor == null) return;
645 if (myUnbuiltNodes.contains(node)) {
646 processUnbuilt(node, descriptor, pass);
647 processNodeActionsIfReady(node);
648 return;
651 if (!forcedNow && getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor))) {
652 queueBackgroundUpdate(node, descriptor, pass);
653 return;
656 final MutualMap<Object, Integer> elementToIndexMap = collectElementToIndexMap(descriptor, preloadedChildren);
658 myUpdatingChildren.add(node);
659 processAllChildren(node, elementToIndexMap, pass).doWhenDone(new Runnable() {
660 public void run() {
661 if (isDisposed(node)) {
662 return;
665 if (canYield()) {
666 removeLoadingNode(node);
669 ArrayList<TreeNode> nodesToInsert = collectNodesToInsert(descriptor, elementToIndexMap);
671 insertNodesInto(nodesToInsert, node);
673 updateNodesToInsert(nodesToInsert, pass);
675 if (wasExpanded) {
676 expand(node);
679 if (wasExpanded || wasLeaf) {
680 expand(node, descriptor, wasLeaf);
683 myUpdatingChildren.remove(node);
685 final Object element = getElementFor(node);
686 addNodeAction(element, new NodeAction() {
687 public void onReady(final DefaultMutableTreeNode node) {
688 removeLoadingNode(node);
692 processNodeActionsIfReady(node);
697 private boolean isDisposed(DefaultMutableTreeNode node) {
698 return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
701 private void expand(DefaultMutableTreeNode node) {
702 expand(new TreePath(node.getPath()));
705 private void expand(final TreePath path) {
706 if (path == null) return;
707 final Object last = path.getLastPathComponent();
708 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
709 final boolean isRoot = last == myTree.getModel().getRoot();
710 final TreePath parent = path.getParentPath();
711 if (false) {
712 processNodeActionsIfReady((DefaultMutableTreeNode)last);
714 else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent))) {
715 if (last instanceof DefaultMutableTreeNode) {
716 processNodeActionsIfReady((DefaultMutableTreeNode)last);
719 else {
720 if (isLeaf && parent != null) {
721 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
722 if (parentNode != null) {
723 myUnbuiltNodes.add(parentNode);
725 expandPath(parent);
727 else {
728 expandPath(path);
733 private void processUnbuilt(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final TreeUpdatePass pass) {
734 if (getBuilder().isAlwaysShowPlus(descriptor)) return; // check for isAlwaysShowPlus is important for e.g. changing Show Members state!
736 final Object element = getBuilder().getTreeStructureElement(descriptor);
738 if (getTreeStructure().isToBuildChildrenInBackground(element)) return; //?
740 final Object[] children = getChildrenFor(element);
741 if (children.length == 0) {
742 removeLoadingNode(node);
744 else if (getBuilder().isAutoExpandNode((NodeDescriptor)node.getUserObject())) {
745 addNodeAction(getElementFor(node), new NodeAction() {
746 public void onReady(final DefaultMutableTreeNode node) {
747 final TreePath path = new TreePath(node.getPath());
748 if (getTree().isExpanded(path) || children.length == 0) {
749 removeLoadingNode(node);
751 else {
752 maybeYeild(new ActiveRunnable() {
753 public ActionCallback run() {
754 expand(element, null);
755 return new ActionCallback.Done();
757 }, pass, node);
764 private void removeLoadingNode(final DefaultMutableTreeNode parent) {
765 for (int i = 0; i < parent.getChildCount(); i++) {
766 if (removeIfLoading(parent.getChildAt(i))) break;
768 myUnbuiltNodes.remove(parent);
771 private boolean removeIfLoading(TreeNode node) {
772 if (isLoadingNode(node)) {
773 removeNodeFromParent((MutableTreeNode)node, false);
774 return true;
777 return false;
780 //todo [kirillk] temporary consistency check
781 private Object[] getChildrenFor(final Object element) {
782 final Object[] passOne;
783 try {
784 passOne = getTreeStructure().getChildElements(element);
786 catch (IndexNotReadyException e) {
787 if (!myWasEverIndexNotReady) {
788 myWasEverIndexNotReady = true;
789 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
791 return ArrayUtil.EMPTY_OBJECT_ARRAY;
794 if (!myCheckStructure) return passOne;
796 final Object[] passTwo = getTreeStructure().getChildElements(element);
798 final HashSet two = new HashSet(Arrays.asList(passTwo));
800 if (passOne.length != passTwo.length) {
801 LOG.error(
802 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
803 element);
805 else {
806 for (Object eachInOne : passOne) {
807 if (!two.contains(eachInOne)) {
808 LOG.error(
809 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
810 element);
811 break;
816 return passOne;
819 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert, TreeUpdatePass pass) {
820 for (TreeNode aNodesToInsert : nodesToInsert) {
821 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
822 addLoadingNode(childNode);
823 updateNodeChildren(childNode, pass, null, false);
827 private ActionCallback processAllChildren(final DefaultMutableTreeNode node,
828 final MutualMap<Object, Integer> elementToIndexMap,
829 final TreeUpdatePass pass) {
831 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
832 return maybeYeild(new ActiveRunnable() {
833 public ActionCallback run() {
834 return processAllChildren(node, elementToIndexMap, pass, childNodes);
836 }, pass, node);
839 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
840 final ActionCallback result = new ActionCallback();
842 if (isToYieldUpdateFor(node)) {
843 pass.setCurrentNode(node);
844 yieldAndRun(new Runnable() {
845 public void run() {
846 if (pass.isExpired()) return;
848 if (getUpdater().isRerunNeededFor(pass)) {
849 getUpdater().addSubtreeToUpdate(pass);
850 result.setRejected();
852 else {
853 processRunnable.run().notify(result);
856 }, pass);
858 else {
859 processRunnable.run().notify(result);
862 return result;
865 private void yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
866 myYeildingPasses.add(pass);
867 myYeildingNow = true;
868 yield(new Runnable() {
869 public void run() {
870 if (isReleased()) {
871 return;
874 runOnYeildingDone(new Runnable() {
875 public void run() {
876 if (isReleased()) {
877 return;
879 executeYeildingRequest(runnable, pass);
886 public boolean isYeildingNow() {
887 return myYeildingNow;
890 private boolean hasSheduledUpdates() {
891 return getUpdater().hasNodesToUpdate() || myLoadingParents.size() > 0;
894 private boolean hasExpandedUnbuiltNodes() {
895 for (DefaultMutableTreeNode each : myUnbuiltNodes) {
896 if (myTree.isExpanded(new TreePath(each.getPath()))) return true;
899 return false;
902 public boolean isReady() {
903 return !isYeildingNow() && !hasSheduledUpdates() && !hasExpandedUnbuiltNodes() && !isWorkerBusy();
906 private void executeYeildingRequest(Runnable runnable, TreeUpdatePass pass) {
907 try {
908 myYeildingPasses.remove(pass);
909 runnable.run();
911 finally {
912 maybeYeildingFinished();
916 private void maybeYeildingFinished() {
917 if (myYeildingPasses.size() == 0) {
918 myYeildingNow = false;
919 flushPendingNodeActions();
923 private void flushPendingNodeActions() {
924 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
925 myPendingNodeActions.clear();
927 for (DefaultMutableTreeNode each : nodes) {
928 processNodeActionsIfReady(each);
931 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
932 for (Runnable each : actions) {
933 if (!isYeildingNow()) {
934 myYeildingDoneRunnables.remove(each);
935 each.run();
940 protected void runOnYeildingDone(Runnable onDone) {
941 getBuilder().runOnYeildingDone(onDone);
944 protected void yield(Runnable runnable) {
945 getBuilder().yield(runnable);
948 private ActionCallback processAllChildren(final DefaultMutableTreeNode node,
949 final MutualMap<Object, Integer> elementToIndexMap,
950 final TreeUpdatePass pass,
951 final ArrayList<TreeNode> childNodes) {
954 if (pass.isExpired()) return new ActionCallback.Rejected();
956 if (childNodes.size() == 0) return new ActionCallback.Done();
959 final ActionCallback result = new ActionCallback(childNodes.size());
961 for (TreeNode childNode1 : childNodes) {
962 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)childNode1;
963 if (isLoadingNode(childNode)) {
964 result.setDone();
965 continue;
968 maybeYeild(new ActiveRunnable() {
969 @Override
970 public ActionCallback run() {
971 return processChildNode(childNode, (NodeDescriptor)childNode.getUserObject(), node, elementToIndexMap, pass);
973 }, pass, node).notify(result);
976 return result;
979 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
980 if (!canYield()) return false;
981 return getBuilder().isToYieldUpdateFor(node);
984 private MutualMap<Object, Integer> collectElementToIndexMap(final NodeDescriptor descriptor, @Nullable Object[] preloadedChildren) {
985 MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
986 Object[] children = preloadedChildren != null ? preloadedChildren : getChildrenFor(getBuilder().getTreeStructureElement(descriptor));
987 int index = 0;
988 for (Object child : children) {
989 if (!isValid(child)) continue;
990 elementToIndexMap.put(child, Integer.valueOf(index));
991 index++;
993 return elementToIndexMap;
996 private void expand(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final boolean wasLeaf) {
997 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
998 alarm.addRequest(new Runnable() {
999 public void run() {
1000 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1002 }, WAIT_CURSOR_DELAY);
1004 if (wasLeaf && getBuilder().isAutoExpandNode(descriptor)) {
1005 expand(node);
1008 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1009 for (TreeNode node1 : nodes) {
1010 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1011 if (isLoadingNode(childNode)) continue;
1012 NodeDescriptor childDescr = (NodeDescriptor)childNode.getUserObject();
1013 if (getBuilder().isAutoExpandNode(childDescr)) {
1014 addNodeAction(getElementFor(childNode), new NodeAction() {
1015 public void onReady(DefaultMutableTreeNode node) {
1016 expand(childNode);
1019 addSubtreeToUpdate(childNode);
1023 int n = alarm.cancelAllRequests();
1024 if (n == 0) {
1025 myTree.setCursor(Cursor.getDefaultCursor());
1029 public static boolean isLoadingNode(final Object node) {
1030 return node instanceof LoadingNode;
1033 private ArrayList<TreeNode> collectNodesToInsert(final NodeDescriptor descriptor, final MutualMap<Object, Integer> elementToIndexMap) {
1034 ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1035 final Collection<Object> allElements = elementToIndexMap.getKeys();
1036 for (Object child : allElements) {
1037 Integer index = elementToIndexMap.getValue(child);
1038 final NodeDescriptor childDescr = getTreeStructure().createDescriptor(child, descriptor);
1039 //noinspection ConstantConditions
1040 if (childDescr == null) {
1041 LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1042 continue;
1044 childDescr.setIndex(index.intValue());
1045 update(childDescr, false);
1046 if (getElementFromDescriptor(childDescr) == null) {
1047 LOG.error("childDescr.getElement() == null, child = " + child + ", builder = " + this);
1048 continue;
1050 final DefaultMutableTreeNode childNode = createChildNode(childDescr);
1051 nodesToInsert.add(childNode);
1052 createMapping(getElementFromDescriptor(childDescr), childNode);
1055 return nodesToInsert;
1058 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1059 return new ElementNode(this, descriptor);
1062 protected boolean canYield() {
1063 return myCanYield && myYeildingUpdate.asBoolean();
1066 public long getClearOnHideDelay() {
1067 return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
1070 static class ElementNode extends DefaultMutableTreeNode {
1072 Set<Object> myElements = new HashSet<Object>();
1073 AbstractTreeUi myUi;
1076 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1077 super(descriptor);
1078 myUi = ui;
1081 @Override
1082 public void insert(final MutableTreeNode newChild, final int childIndex) {
1083 super.insert(newChild, childIndex);
1084 final Object element = myUi.getElementFor(newChild);
1085 if (element != null) {
1086 myElements.add(element);
1090 @Override
1091 public void remove(final int childIndex) {
1092 final TreeNode node = getChildAt(childIndex);
1093 super.remove(childIndex);
1094 final Object element = myUi.getElementFor(node);
1095 if (element != null) {
1096 myElements.remove(element);
1100 boolean isValidChild(Object childElement) {
1101 return myElements.contains(childElement);
1104 @Override
1105 public String toString() {
1106 return String.valueOf(getUserObject());
1110 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1111 DefaultMutableTreeNode eachParent = kid;
1112 while (eachParent != null) {
1113 if (myUpdatingChildren.contains(eachParent)) return true;
1114 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1117 return false;
1120 private boolean queueBackgroundUpdate(final DefaultMutableTreeNode node, final NodeDescriptor descriptor, final TreeUpdatePass pass) {
1121 if (myLoadingParents.contains(getElementFromDescriptor(descriptor))) return false;
1123 myLoadingParents.add(getElementFromDescriptor(descriptor));
1125 if (!isNodeBeingBuilt(node)) {
1126 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1127 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1130 final Ref<Object[]> children = new Ref<Object[]>();
1131 Runnable buildRunnable = new Runnable() {
1132 public void run() {
1133 if (isReleased()) return;
1135 update(descriptor, true);
1136 Object element = getElementFromDescriptor(descriptor);
1137 if (element == null) return;
1139 children.set(getChildrenFor(getBuilder().getTreeStructureElement(descriptor))); // load children
1143 final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
1144 Runnable updateRunnable = new Runnable() {
1145 public void run() {
1146 if (isReleased()) return;
1147 if (children.get() == null) return;
1149 updateNodeChildren(node, pass, children.get(), true);
1151 myLoadingParents.remove(getElementFromDescriptor(descriptor));
1153 update(descriptor, false);
1154 Object element = getElementFromDescriptor(descriptor);
1156 if (element != null) {
1157 myUnbuiltNodes.remove(node);
1159 for (int i = 0; i < node.getChildCount(); i++) {
1160 TreeNode child = node.getChildAt(i);
1161 if (isLoadingNode(child)) {
1162 //if (TreeBuilderUtil.isNodeSelected(myTree, node)) {
1163 // addSelectionPath(new TreePath(myTreeModel.getPathToRoot(node)), true, Condition.FALSE);
1165 removeIfLoading(child);
1166 i--;
1170 nodeToProcessActions[0] = node;
1174 addTaskToWorker(buildRunnable, true, updateRunnable, new Runnable() {
1175 public void run() {
1176 if (nodeToProcessActions[0] != null) {
1177 processNodeActionsIfReady(nodeToProcessActions[0]);
1181 return true;
1184 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1185 if (isNodeBeingBuilt(node)) return;
1187 final Object o = node.getUserObject();
1188 if (!(o instanceof NodeDescriptor)) return;
1191 if (isYeildingNow()) {
1192 myPendingNodeActions.add(node);
1193 return;
1196 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1198 final List<NodeAction> actions = myNodeActions.get(element);
1199 if (actions != null) {
1200 myNodeActions.remove(element);
1201 for (NodeAction each : actions) {
1202 each.onReady(node);
1206 if (!isUpdatingParent(node) && !isWorkerBusy()) {
1207 final UpdaterTreeState state = myUpdaterState;
1208 if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1209 if (!state.restore()) {
1210 setUpdaterState(state);
1217 private void processSmartExpand(final DefaultMutableTreeNode node) {
1218 if (getBuilder().isSmartExpand() && node.getChildCount() == 1) { // "smart" expand
1219 TreeNode childNode = node.getChildAt(0);
1220 if (isLoadingNode(childNode)) return;
1221 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(childNode);
1222 expand(childPath);
1226 public boolean isLoadingChildrenFor(final Object nodeObject) {
1227 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1229 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1231 int loadingNodes = 0;
1232 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
1233 TreeNode child = node.getChildAt(i);
1234 if (isLoadingNode(child)) {
1235 loadingNodes++;
1238 return loadingNodes > 0 && loadingNodes == node.getChildCount();
1241 private boolean isParentLoading(Object nodeObject) {
1242 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1244 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1246 TreeNode eachParent = node.getParent();
1248 while (eachParent != null) {
1249 eachParent = eachParent.getParent();
1250 if (eachParent instanceof DefaultMutableTreeNode) {
1251 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
1252 if (myLoadingParents.contains(eachElement)) return true;
1256 return false;
1259 protected String getLoadingNodeText() {
1260 return IdeBundle.message("progress.searching");
1263 private ActionCallback processChildNode(final DefaultMutableTreeNode childNode,
1264 final NodeDescriptor childDescriptor,
1265 final DefaultMutableTreeNode parentNode,
1266 final MutualMap<Object, Integer> elementToIndexMap,
1267 TreeUpdatePass pass) {
1269 if (pass.isExpired()) {
1270 return new ActionCallback.Rejected();
1273 NodeDescriptor childDesc = childDescriptor;
1276 if (childDesc == null) {
1277 pass.expire();
1278 return new ActionCallback.Rejected();
1280 Object oldElement = getElementFromDescriptor(childDesc);
1281 if (oldElement == null) {
1282 pass.expire();
1283 return new ActionCallback.Rejected();
1285 boolean changes = update(childDesc, false);
1286 boolean forceRemapping = false;
1287 Object newElement = getElementFromDescriptor(childDesc);
1289 Integer index = newElement != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc)) : null;
1290 if (index != null) {
1291 final Object elementFromMap = elementToIndexMap.getKey(index);
1292 if (elementFromMap != newElement && elementFromMap.equals(newElement)) {
1293 if (isInStructure(elementFromMap) && isInStructure(newElement)) {
1294 if (parentNode.getUserObject() instanceof NodeDescriptor) {
1295 final NodeDescriptor parentDescriptor = (NodeDescriptor)parentNode.getUserObject();
1296 childDesc = getTreeStructure().createDescriptor(elementFromMap, parentDescriptor);
1297 childNode.setUserObject(childDesc);
1298 newElement = elementFromMap;
1299 forceRemapping = true;
1300 update(childDesc, false);
1301 changes = true;
1306 if (childDesc.getIndex() != index.intValue()) {
1307 changes = true;
1309 childDesc.setIndex(index.intValue());
1312 if (index != null && changes) {
1313 updateNodeImageAndPosition(childNode);
1315 if (!oldElement.equals(newElement) | forceRemapping) {
1316 removeMapping(oldElement, childNode, newElement);
1317 if (newElement != null) {
1318 createMapping(newElement, childNode);
1322 if (index == null) {
1323 int selectedIndex = -1;
1324 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
1325 selectedIndex = parentNode.getIndex(childNode);
1328 if (childNode.getParent() instanceof DefaultMutableTreeNode) {
1329 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
1330 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
1331 if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
1332 insertLoadingNode(parent, false);
1337 Object disposedElement = getElementFor(childNode);
1339 removeNodeFromParent(childNode, selectedIndex >= 0);
1340 disposeNode(childNode);
1342 if (selectedIndex >= 0) {
1343 if (parentNode.getChildCount() > 0) {
1344 if (parentNode.getChildCount() > selectedIndex) {
1345 TreeNode newChildNode = parentNode.getChildAt(selectedIndex);
1346 if (isValidForSelectionAdjusting(newChildNode)) {
1347 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement));
1350 else {
1351 TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1);
1352 if (isValidForSelectionAdjusting(newChild)) {
1353 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement));
1357 else {
1358 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement));
1362 else {
1363 elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc));
1364 updateNodeChildren(childNode, pass, null, false);
1367 if (parentNode.equals(getRootNode())) {
1368 myTreeModel.nodeChanged(getRootNode());
1371 return new ActionCallback.Done();
1374 private boolean isValidForSelectionAdjusting(TreeNode node) {
1375 if (isLoadingNode(node)) return true;
1377 final Object elementInTree = getElementFor(node);
1378 if (elementInTree == null) return false;
1380 final TreeNode parentNode = node.getParent();
1381 final Object parentElementInTree = getElementFor(parentNode);
1382 if (parentElementInTree == null) return false;
1384 final Object parentElement = getTreeStructure().getParentElement(elementInTree);
1386 return parentElementInTree.equals(parentElement);
1389 private Condition getExpiredElementCondition(final Object element) {
1390 return new Condition() {
1391 public boolean value(final Object o) {
1392 return isInStructure(element);
1397 private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement) {
1398 doWithUpdaterState(new Runnable() {
1399 public void run() {
1400 TreePath toSelect = null;
1402 if (isLoadingNode(path.getLastPathComponent())) {
1403 final TreePath parentPath = path.getParentPath();
1404 if (parentPath != null) {
1405 toSelect = parentPath;
1408 else {
1409 toSelect = path;
1412 if (toSelect != null) {
1413 myTree.addSelectionPath(toSelect);
1415 if (isAdjustedSelection && myUpdaterState != null) {
1416 final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
1417 myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement);
1424 private static TreePath getPathFor(TreeNode node) {
1425 if (node instanceof DefaultMutableTreeNode) {
1426 return new TreePath(((DefaultMutableTreeNode)node).getPath());
1428 else {
1429 ArrayList nodes = new ArrayList();
1430 TreeNode eachParent = node;
1431 while (eachParent != null) {
1432 nodes.add(eachParent);
1433 eachParent = eachParent.getParent();
1436 return new TreePath(ArrayUtil.toObjectArray(nodes));
1441 private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
1442 doWithUpdaterState(new Runnable() {
1443 public void run() {
1444 if (willAdjustSelection) {
1445 final TreePath path = getPathFor(node);
1446 if (myTree.isPathSelected(path)) {
1447 myTree.removeSelectionPath(path);
1451 myTreeModel.removeNodeFromParent(node);
1456 private void expandPath(final TreePath path) {
1457 doWithUpdaterState(new Runnable() {
1458 public void run() {
1459 myTree.expandPath(path);
1464 private void doWithUpdaterState(Runnable runnable) {
1465 if (myUpdaterState != null) {
1466 myUpdaterState.process(runnable);
1468 else {
1469 runnable.run();
1473 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
1474 return descriptor.update();
1477 private void addLoadingNode(final DefaultMutableTreeNode node) {
1478 final boolean[] hasNoChildren = new boolean[1];
1479 final NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1480 if (!getBuilder().isAlwaysShowPlus(descriptor)) {
1481 Runnable updateRunnable = new Runnable() {
1482 public void run() {
1483 if (isReleased()) return;
1485 if (hasNoChildren[0]) {
1486 update(descriptor, false);
1487 removeLoadingNode(node);
1492 if (getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor))) {
1493 Runnable buildRunnable = new Runnable() {
1494 public void run() {
1495 if (isReleased()) return;
1497 update(descriptor, true);
1498 Object element = getBuilder().getTreeStructureElement(descriptor);
1499 if (element == null && !isValid(element)) return;
1501 Object[] children = getChildrenFor(element);
1502 hasNoChildren[0] = children.length == 0;
1505 addTaskToWorker(buildRunnable, false, updateRunnable, new Runnable() {
1506 public void run() {
1507 processNodeActionsIfReady(node);
1511 else {
1512 Object[] children = getChildrenFor(getBuilder().getTreeStructureElement(descriptor));
1513 if (children.length == 0) return;
1517 insertLoadingNode(node, true);
1520 private boolean isValid(Object element) {
1521 if (element instanceof ValidateableNode) {
1522 if (!((ValidateableNode)element).isValid()) return false;
1524 return getBuilder().validateNode(element);
1527 private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
1528 myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
1529 if (addToUnbuilt) {
1530 myUnbuiltNodes.add(node);
1535 protected void addTaskToWorker(@NotNull final Runnable bgReadActionRunnable,
1536 boolean first,
1537 @Nullable final Runnable edtPostRunnable,
1538 @Nullable final Runnable finilizeEdtRunnable) {
1539 registerWorkerTask(bgReadActionRunnable);
1541 final Runnable pooledThreadWithProgressRunnable = new Runnable() {
1542 public void run() {
1543 if (isReleased()) {
1544 return;
1547 getBuilder().runBackgroundLoading(new Runnable() {
1548 public void run() {
1549 if (isReleased()) {
1550 return;
1553 try {
1554 bgReadActionRunnable.run();
1556 if (edtPostRunnable != null) {
1557 getBuilder().updateAfterLoadedInBackground(new Runnable() {
1558 public void run() {
1559 try {
1560 edtPostRunnable.run();
1562 finally {
1563 unregisterWorkerTask(bgReadActionRunnable, finilizeEdtRunnable);
1568 else {
1569 unregisterWorkerTask(bgReadActionRunnable, finilizeEdtRunnable);
1572 catch (ProcessCanceledException e) {
1573 unregisterWorkerTask(bgReadActionRunnable, finilizeEdtRunnable);
1575 catch (Throwable t) {
1576 unregisterWorkerTask(bgReadActionRunnable, finilizeEdtRunnable);
1577 throw new RuntimeException(t);
1584 Runnable pooledThreadRunnable = new Runnable() {
1585 public void run() {
1586 if (isReleased()) return;
1588 try {
1589 if (myProgress != null) {
1590 ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress);
1592 else {
1593 pooledThreadWithProgressRunnable.run();
1596 catch (ProcessCanceledException e) {
1597 //ignore
1602 if (myWorker == null || myWorker.isDisposed()) {
1603 myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
1604 myWorker.start();
1605 if (first) {
1606 myWorker.addTaskFirst(pooledThreadRunnable);
1608 else {
1609 myWorker.addTask(pooledThreadRunnable);
1611 myWorker.dispose(false);
1613 else {
1614 if (first) {
1615 myWorker.addTaskFirst(pooledThreadRunnable);
1617 else {
1618 myWorker.addTask(pooledThreadRunnable);
1623 private void registerWorkerTask(Runnable runnable) {
1624 synchronized (myActiveWorkerTasks) {
1625 myActiveWorkerTasks.add(runnable);
1629 private void unregisterWorkerTask(Runnable runnable, @Nullable Runnable finalizeRunnable) {
1630 boolean wasRemoved;
1631 synchronized (myActiveWorkerTasks) {
1632 wasRemoved = myActiveWorkerTasks.remove(runnable);
1635 if (wasRemoved && finalizeRunnable != null) {
1636 finalizeRunnable.run();
1640 public boolean isWorkerBusy() {
1641 synchronized (myActiveWorkerTasks) {
1642 return myActiveWorkerTasks.size() > 0;
1646 private void clearWorkerTasks() {
1647 synchronized (myActiveWorkerTasks) {
1648 myActiveWorkerTasks.clear();
1652 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node) {
1653 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
1654 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1655 if (getElementFromDescriptor(descriptor) == null) return;
1656 DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
1657 if (parentNode != null) {
1658 int oldIndex = parentNode.getIndex(node);
1659 int newIndex = oldIndex;
1660 if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
1661 final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
1662 for (int i = 0; i < parentNode.getChildCount(); i++) {
1663 children.add(parentNode.getChildAt(i));
1665 Collections.sort(children, myNodeComparator);
1666 newIndex = children.indexOf(node);
1669 if (oldIndex != newIndex) {
1670 List<Object> pathsToExpand = new ArrayList<Object>();
1671 List<Object> selectionPaths = new ArrayList<Object>();
1672 TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
1673 removeNodeFromParent(node, false);
1674 myTreeModel.insertNodeInto(node, parentNode, newIndex);
1675 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
1677 else {
1678 myTreeModel.nodeChanged(node);
1681 else {
1682 myTreeModel.nodeChanged(node);
1686 public DefaultTreeModel getTreeModel() {
1687 return myTreeModel;
1690 private void insertNodesInto(ArrayList<TreeNode> nodes, DefaultMutableTreeNode parentNode) {
1691 if (nodes.isEmpty()) return;
1693 nodes = new ArrayList<TreeNode>(nodes);
1694 Collections.sort(nodes, myNodeComparator);
1696 ArrayList<TreeNode> all = TreeUtil.childrenToArray(parentNode);
1697 all.addAll(nodes);
1698 Collections.sort(all, myNodeComparator);
1700 int[] indices = new int[nodes.size()];
1701 int idx = 0;
1702 for (int i = 0; i < nodes.size(); i++) {
1703 TreeNode node = nodes.get(i);
1704 while (all.get(idx) != node) idx++;
1705 indices[i] = idx;
1706 parentNode.insert((MutableTreeNode)node, idx);
1709 myTreeModel.nodesWereInserted(parentNode, indices);
1712 private void disposeNode(DefaultMutableTreeNode node) {
1713 myUpdatingChildren.remove(node);
1714 myUnbuiltNodes.remove(node);
1716 if (node.getChildCount() > 0) {
1717 for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
1718 disposeNode(_node);
1721 if (isLoadingNode(node)) return;
1722 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
1723 if (descriptor == null) return;
1724 final Object element = getElementFromDescriptor(descriptor);
1725 removeMapping(element, node, null);
1726 node.setUserObject(null);
1727 node.removeAllChildren();
1730 public void addSubtreeToUpdate(final DefaultMutableTreeNode root) {
1731 addSubtreeToUpdate(root, null);
1734 public void addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
1735 getUpdater().runAfterUpdate(runAfterUpdate);
1736 getUpdater().addSubtreeToUpdate(root);
1739 public boolean wasRootNodeInitialized() {
1740 return myRootNodeWasInitialized;
1743 public void select(final Object[] elements, @Nullable final Runnable onDone) {
1744 select(elements, onDone, false);
1747 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
1748 select(elements, onDone, addToSelection, false);
1751 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) {
1752 _select(elements, onDone, addToSelection, true, false, deferred);
1755 void _select(final Object[] elements,
1756 final Runnable onDone,
1757 final boolean addToSelection,
1758 final boolean checkCurrentSelection,
1759 final boolean checkIfInStructure) {
1761 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, false);
1764 void _select(final Object[] elements,
1765 final Runnable onDone,
1766 final boolean addToSelection,
1767 final boolean checkCurrentSelection,
1768 final boolean checkIfInStructure,
1769 final boolean deferred) {
1771 boolean willAffectSelection = elements.length > 0 || (elements.length == 0 && addToSelection);
1772 if (!willAffectSelection) {
1773 runDone(onDone);
1774 return;
1777 final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections;
1779 if (!deferred && wasRootNodeInitialized() && willAffectSelection) {
1780 myCanProcessDeferredSelections = false;
1783 if (!checkDeferred(deferred, onDone)) return;
1785 if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) {
1786 getTree().clearSelection();
1790 runDone(new Runnable() {
1791 public void run() {
1792 if (!checkDeferred(deferred, onDone)) return;
1794 final Set<Object> currentElements = getSelectedElements();
1796 if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
1797 boolean runSelection = false;
1798 for (Object eachToSelect : elements) {
1799 if (!currentElements.contains(eachToSelect)) {
1800 runSelection = true;
1801 break;
1805 if (!runSelection) {
1806 if (elements.length > 0) {
1807 selectVisible(elements[0], onDone, true);
1809 return;
1813 Set<Object> toSelect = new HashSet<Object>();
1814 myTree.clearSelection();
1815 toSelect.addAll(Arrays.asList(elements));
1816 if (addToSelection) {
1817 toSelect.addAll(currentElements);
1820 if (checkIfInStructure) {
1821 final Iterator<Object> allToSelect = toSelect.iterator();
1822 while (allToSelect.hasNext()) {
1823 Object each = allToSelect.next();
1824 if (!isInStructure(each)) {
1825 allToSelect.remove();
1830 final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
1832 if (wasRootNodeInitialized()) {
1833 final int[] originalRows = myTree.getSelectionRows();
1834 if (!addToSelection) {
1835 myTree.clearSelection();
1837 addNext(elementsToSelect, 0, onDone, originalRows, deferred);
1839 else {
1840 myDeferredSelections.clear();
1841 myDeferredSelections.add(new Runnable() {
1842 public void run() {
1843 select(elementsToSelect, onDone, false, true);
1851 private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) {
1852 if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) {
1853 return true;
1854 } else {
1855 runDone(onDone);
1856 return false;
1860 @NotNull
1861 final Set<Object> getSelectedElements() {
1862 final TreePath[] paths = myTree.getSelectionPaths();
1864 Set<Object> result = new HashSet<Object>();
1865 if (paths != null) {
1866 for (TreePath eachPath : paths) {
1867 if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
1868 final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
1869 final Object eachElement = getElementFor(eachNode);
1870 if (eachElement != null) {
1871 result.add(eachElement);
1876 return result;
1880 private void addNext(final Object[] elements, final int i, @Nullable final Runnable onDone, final int[] originalRows, final boolean deferred) {
1881 if (i >= elements.length) {
1882 if (myTree.isSelectionEmpty()) {
1883 myTree.setSelectionRows(originalRows);
1885 runDone(onDone);
1887 else {
1888 if (!checkDeferred(deferred, onDone)) {
1889 return;
1892 doSelect(elements[i], new Runnable() {
1893 public void run() {
1894 if (!checkDeferred(deferred, onDone)) return;
1896 addNext(elements, i + 1, onDone, originalRows, deferred);
1898 }, true, deferred);
1902 public void select(final Object element, @Nullable final Runnable onDone) {
1903 select(element, onDone, false);
1906 public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
1907 _select(new Object[] {element}, onDone, addToSelection, true, false);
1910 private void doSelect(final Object element, final Runnable onDone, final boolean addToSelection, final boolean deferred) {
1911 final Runnable _onDone = new Runnable() {
1912 public void run() {
1913 if (!checkDeferred(deferred, onDone)) return;
1914 selectVisible(element, onDone, addToSelection);
1917 _expand(element, _onDone, true, false);
1920 private void selectVisible(Object element, final Runnable onDone, boolean addToSelection) {
1921 final DefaultMutableTreeNode toSelect = getNodeForElement(element, false);
1922 if (toSelect == null) {
1923 runDone(onDone);
1924 return;
1926 final int row = myTree.getRowForPath(new TreePath(toSelect.getPath()));
1928 if (myUpdaterState != null) {
1929 myUpdaterState.addSelection(element);
1931 TreeUtil.showAndSelect(myTree, row - 2, row + 2, row, -1, addToSelection).doWhenDone(new Runnable() {
1932 public void run() {
1933 runDone(onDone);
1938 public void expand(final Object element, @Nullable final Runnable onDone) {
1939 expand(element, onDone, false);
1943 void expand(final Object element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
1944 _expand(new Object[]{element}, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure);
1947 void expand(final Object[] element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
1948 _expand(element, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure);
1951 void _expand(final Object[] element, @NotNull final Runnable onDone, final boolean parentsOnly, final boolean checkIfInStructure) {
1952 runDone(new Runnable() {
1953 public void run() {
1954 if (element.length == 0) {
1955 runDone(onDone);
1956 return;
1959 if (myUpdaterState != null) {
1960 myUpdaterState.clearExpansion();
1964 final ActionCallback done = new ActionCallback(element.length);
1965 done.doWhenDone(new Runnable() {
1966 public void run() {
1967 runDone(onDone);
1971 for (final Object toExpand : element) {
1972 _expand(toExpand, new Runnable() {
1973 public void run() {
1974 done.setDone();
1976 }, parentsOnly, checkIfInStructure);
1982 public void collapseChildren(final Object element, @Nullable final Runnable onDone) {
1983 runDone(new Runnable() {
1984 public void run() {
1985 final DefaultMutableTreeNode node = getNodeForElement(element, false);
1986 if (node != null) {
1987 getTree().collapsePath(new TreePath(node.getPath()));
1988 runDone(onDone);
1994 private void runDone(@Nullable Runnable done) {
1995 if (done == null) return;
1997 if (isYeildingNow()) {
1998 if (!myYeildingDoneRunnables.contains(done)) {
1999 myYeildingDoneRunnables.add(done);
2002 else {
2003 done.run();
2007 private void _expand(final Object element, @NotNull final Runnable onDone, final boolean parentsOnly, boolean checkIfInStructure) {
2008 if (checkIfInStructure && !isInStructure(element)) {
2009 runDone(onDone);
2010 return;
2013 if (wasRootNodeInitialized()) {
2014 List<Object> kidsToExpand = new ArrayList<Object>();
2015 Object eachElement = element;
2016 DefaultMutableTreeNode firstVisible = null;
2017 while (true) {
2018 if (!isValid(eachElement)) break;
2020 firstVisible = getNodeForElement(eachElement, true);
2021 if (eachElement != element || !parentsOnly) {
2022 assert !kidsToExpand.contains(eachElement) :
2023 "Not a valid tree structure, walking up the structure gives many entries for element=" +
2024 eachElement +
2025 ", root=" +
2026 getTreeStructure().getRootElement();
2027 kidsToExpand.add(eachElement);
2029 if (firstVisible != null) break;
2030 eachElement = getTreeStructure().getParentElement(eachElement);
2031 if (eachElement == null) {
2032 firstVisible = null;
2033 break;
2037 if (firstVisible == null) {
2038 runDone(onDone);
2040 else if (kidsToExpand.size() == 0) {
2041 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)firstVisible.getParent();
2042 if (parentNode != null) {
2043 final TreePath parentPath = new TreePath(parentNode.getPath());
2044 if (!myTree.isExpanded(parentPath)) {
2045 expand(parentPath);
2048 runDone(onDone);
2050 else {
2051 processExpand(firstVisible, kidsToExpand, kidsToExpand.size() - 1, onDone);
2054 else {
2055 myDeferredExpansions.add(new Runnable() {
2056 public void run() {
2057 _expand(element, onDone, parentsOnly, false);
2063 private void processExpand(final DefaultMutableTreeNode toExpand,
2064 final List kidsToExpand,
2065 final int expandIndex,
2066 @NotNull final Runnable onDone) {
2067 final Object element = getElementFor(toExpand);
2068 if (element == null) {
2069 runDone(onDone);
2070 return;
2073 addNodeAction(element, new NodeAction() {
2074 public void onReady(final DefaultMutableTreeNode node) {
2075 if (node.getChildCount() >= 0 && !myTree.isExpanded(new TreePath(node.getPath()))) {
2076 final ArrayList list = new ArrayList();
2077 for (int i = 0; i < node.getChildCount(); i++) {
2078 list.add(node.getChildAt(i));
2080 expand(node);
2083 if (expandIndex < 0) {
2084 runDone(onDone);
2085 return;
2088 final DefaultMutableTreeNode nextNode = getNodeForElement(kidsToExpand.get(expandIndex), false);
2089 if (nextNode != null) {
2090 processExpand(nextNode, kidsToExpand, expandIndex - 1, onDone);
2092 else {
2093 runDone(onDone);
2098 expand(toExpand);
2102 @Nullable
2103 private Object getElementFor(Object node) {
2104 if (!(node instanceof DefaultMutableTreeNode)) return null;
2105 return getElementFor((DefaultMutableTreeNode)node);
2108 @Nullable
2109 private Object getElementFor(DefaultMutableTreeNode node) {
2110 if (node != null) {
2111 final Object o = node.getUserObject();
2112 if (o instanceof NodeDescriptor) {
2113 return getElementFromDescriptor(((NodeDescriptor)o));
2117 return null;
2120 public final boolean isNodeBeingBuilt(final TreePath path) {
2121 return isNodeBeingBuilt(path.getLastPathComponent());
2124 public final boolean isNodeBeingBuilt(Object node) {
2125 if (isParentLoading(node) || isLoadingParent(node)) return true;
2127 final boolean childrenAreNoLoadedYet = isLoadingChildrenFor(node) && myUnbuiltNodes.contains(node);
2128 if (childrenAreNoLoadedYet) {
2129 if (node instanceof DefaultMutableTreeNode) {
2130 final TreePath nodePath = new TreePath(((DefaultMutableTreeNode)node).getPath());
2131 if (!myTree.isExpanded(nodePath)) return false;
2134 return true;
2138 return false;
2141 private boolean isLoadingParent(Object node) {
2142 if (!(node instanceof DefaultMutableTreeNode)) return false;
2143 return myLoadingParents.contains(getElementFor((DefaultMutableTreeNode)node));
2146 public void setTreeStructure(final AbstractTreeStructure treeStructure) {
2147 myTreeStructure = treeStructure;
2148 clearUpdaterState();
2151 public AbstractTreeUpdater getUpdater() {
2152 return myUpdater;
2155 public void setUpdater(final AbstractTreeUpdater updater) {
2156 myUpdater = updater;
2159 public DefaultMutableTreeNode getRootNode() {
2160 return myRootNode;
2163 public void setRootNode(@NotNull final DefaultMutableTreeNode rootNode) {
2164 myRootNode = rootNode;
2167 private void dropUpdaterStateIfExternalChange() {
2168 if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) {
2169 clearUpdaterState();
2173 private void clearUpdaterState() {
2174 myUpdaterState = null;
2177 private void createMapping(Object element, DefaultMutableTreeNode node) {
2178 if (!myElementToNodeMap.containsKey(element)) {
2179 myElementToNodeMap.put(element, node);
2181 else {
2182 final Object value = myElementToNodeMap.get(element);
2183 final List<DefaultMutableTreeNode> nodes;
2184 if (value instanceof DefaultMutableTreeNode) {
2185 nodes = new ArrayList<DefaultMutableTreeNode>();
2186 nodes.add((DefaultMutableTreeNode)value);
2187 myElementToNodeMap.put(element, nodes);
2189 else {
2190 nodes = (List<DefaultMutableTreeNode>)value;
2192 nodes.add(node);
2196 private void removeMapping(Object element, DefaultMutableTreeNode node, @Nullable Object elementToPutNodeActionsFor) {
2197 final Object value = myElementToNodeMap.get(element);
2198 if (value != null) {
2199 if (value instanceof DefaultMutableTreeNode) {
2200 if (value.equals(node)) {
2201 myElementToNodeMap.remove(element);
2204 else {
2205 List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
2206 final boolean reallyRemoved = nodes.remove(node);
2207 if (reallyRemoved) {
2208 if (nodes.isEmpty()) {
2209 myElementToNodeMap.remove(element);
2215 final List<NodeAction> actions = myNodeActions.get(element);
2216 myNodeActions.remove(element);
2218 if (elementToPutNodeActionsFor != null) {
2219 myNodeActions.put(elementToPutNodeActionsFor, actions);
2223 private DefaultMutableTreeNode getFirstNode(Object element) {
2224 return findNode(element, 0);
2227 private DefaultMutableTreeNode findNode(final Object element, int startIndex) {
2228 final Object value = getBuilder().findNodeByElement(element);
2229 if (value == null) {
2230 return null;
2232 if (value instanceof DefaultMutableTreeNode) {
2233 return startIndex == 0 ? (DefaultMutableTreeNode)value : null;
2235 final List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
2236 return startIndex < nodes.size() ? nodes.get(startIndex) : null;
2239 protected Object findNodeByElement(Object element) {
2240 if (myElementToNodeMap.containsKey(element)) {
2241 return myElementToNodeMap.get(element);
2244 try {
2245 TREE_NODE_WRAPPER.setValue(element);
2246 return myElementToNodeMap.get(TREE_NODE_WRAPPER);
2248 finally {
2249 TREE_NODE_WRAPPER.setValue(null);
2253 private DefaultMutableTreeNode findNodeForChildElement(DefaultMutableTreeNode parentNode, Object element) {
2254 final Object value = myElementToNodeMap.get(element);
2255 if (value == null) {
2256 return null;
2259 if (value instanceof DefaultMutableTreeNode) {
2260 final DefaultMutableTreeNode elementNode = (DefaultMutableTreeNode)value;
2261 return parentNode.equals(elementNode.getParent()) ? elementNode : null;
2264 final List<DefaultMutableTreeNode> allNodesForElement = (List<DefaultMutableTreeNode>)value;
2265 for (final DefaultMutableTreeNode elementNode : allNodesForElement) {
2266 if (parentNode.equals(elementNode.getParent())) {
2267 return elementNode;
2271 return null;
2274 public void cancelBackgroundLoading() {
2275 if (myWorker != null) {
2276 myWorker.cancelTasks();
2277 clearWorkerTasks();
2279 myNodeActions.clear();
2282 private void addNodeAction(Object element, NodeAction action) {
2283 maybeSetBusyAndScheduleWaiterForReady(true);
2285 List<NodeAction> list = myNodeActions.get(element);
2286 if (list == null) {
2287 list = new ArrayList<NodeAction>();
2288 myNodeActions.put(element, list);
2290 list.add(action);
2293 private void cleanUpNow() {
2294 if (isReleased()) return;
2296 final UpdaterTreeState state = new UpdaterTreeState(this);
2298 myTree.collapsePath(new TreePath(myTree.getModel().getRoot()));
2299 myTree.clearSelection();
2300 getRootNode().removeAllChildren();
2302 myRootNodeWasInitialized = false;
2303 myNodeActions.clear();
2304 myElementToNodeMap.clear();
2305 myDeferredSelections.clear();
2306 myDeferredExpansions.clear();
2307 myLoadingParents.clear();
2308 myUnbuiltNodes.clear();
2309 myUpdateFromRootRequested = true;
2311 if (myWorker != null) {
2312 Disposer.dispose(myWorker);
2313 myWorker = null;
2316 myTree.invalidate();
2318 state.restore();
2321 public AbstractTreeUi setClearOnHideDelay(final long clearOnHideDelay) {
2322 myClearOnHideDelay = clearOnHideDelay;
2323 return this;
2326 public void setJantorPollPeriod(final long time) {
2327 myJanitorPollPeriod = time;
2330 public void setCheckStructure(final boolean checkStructure) {
2331 myCheckStructure = checkStructure;
2334 private class MySelectionListener implements TreeSelectionListener {
2335 public void valueChanged(final TreeSelectionEvent e) {
2336 dropUpdaterStateIfExternalChange();
2340 private class MyExpansionListener implements TreeExpansionListener {
2341 public void treeExpanded(TreeExpansionEvent event) {
2342 dropUpdaterStateIfExternalChange();
2344 TreePath path = event.getPath();
2345 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2347 if (!myUnbuiltNodes.contains(node)) {
2348 return;
2350 myUnbuiltNodes.remove(node);
2353 getBuilder().expandNodeChildren(node);
2355 runDone(new Runnable() {
2356 public void run() {
2357 final Object element = getElementFor(node);
2359 for (int i = 0; i < node.getChildCount(); i++) {
2360 removeIfLoading(node.getChildAt(i));
2363 if (node.getChildCount() == 0) {
2364 addNodeAction(element, new NodeAction() {
2365 public void onReady(final DefaultMutableTreeNode node) {
2366 expand(element, null);
2371 processSmartExpand(node);
2376 public void treeCollapsed(TreeExpansionEvent e) {
2377 final TreePath path = e.getPath();
2378 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2379 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2381 TreePath pathToSelect = null;
2382 if (isSelectionInside(node)) {
2383 pathToSelect = new TreePath(node.getPath());
2387 NodeDescriptor descriptor = (NodeDescriptor)node.getUserObject();
2388 if (getBuilder().isDisposeOnCollapsing(descriptor)) {
2389 runDone(new Runnable() {
2390 public void run() {
2391 if (isDisposed(node)) return;
2393 TreePath nodePath = new TreePath(node.getPath());
2394 if (myTree.isExpanded(nodePath)) return;
2396 removeChildren(node);
2397 addLoadingNode(node);
2400 if (node.equals(getRootNode())) {
2401 addSelectionPath(new TreePath(getRootNode().getPath()), true, Condition.FALSE);
2403 else {
2404 myTreeModel.reload(node);
2408 if (pathToSelect != null && myTree.isSelectionEmpty()) {
2409 addSelectionPath(pathToSelect, true, Condition.FALSE);
2413 private void removeChildren(DefaultMutableTreeNode node) {
2414 EnumerationCopy copy = new EnumerationCopy(node.children());
2415 while (copy.hasMoreElements()) {
2416 disposeNode((DefaultMutableTreeNode)copy.nextElement());
2418 node.removeAllChildren();
2419 myTreeModel.nodeStructureChanged(node);
2422 private boolean isSelectionInside(DefaultMutableTreeNode parent) {
2423 TreePath path = new TreePath(myTreeModel.getPathToRoot(parent));
2424 TreePath[] paths = myTree.getSelectionPaths();
2425 if (paths == null) return false;
2426 for (TreePath path1 : paths) {
2427 if (path.isDescendant(path1)) return true;
2429 return false;
2433 public boolean isInStructure(@Nullable Object element) {
2434 Object eachParent = element;
2435 while (eachParent != null) {
2436 if (getTreeStructure().getRootElement().equals(eachParent)) return true;
2437 eachParent = getTreeStructure().getParentElement(eachParent);
2440 return false;
2443 interface NodeAction {
2444 void onReady(DefaultMutableTreeNode node);
2447 public void setCanYield(final boolean canYield) {
2448 myCanYield = canYield;
2451 public Collection<TreeUpdatePass> getYeildingPasses() {
2452 return myYeildingPasses;
2455 public boolean isBuilt(Object element) {
2456 if (!myElementToNodeMap.containsKey(element)) return false;
2457 final Object node = myElementToNodeMap.get(element);
2458 return !myUnbuiltNodes.contains(node);