Settings tree: selecting & filtering fixes
[fedora-idea.git] / platform / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUi.java
blob5456cccb63c4eb60841895298c7cc36c7bcbf1d2
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 public 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 = getDescriptorFrom(((DefaultMutableTreeNode)n1));
56 NodeDescriptor nodeDescriptor2 = getDescriptorFrom(((DefaultMutableTreeNode)n2));
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 Set<Runnable> myActiveWorkerTasks = new HashSet<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;
80 private final Set<Object> myLoadingParents = new HashSet<Object>();
81 private final Map<Object, List<NodeAction>> myNodeChildrenActions = new HashMap<Object, List<NodeAction>>();
83 private long myClearOnHideDelay = -1;
84 private ScheduledExecutorService ourClearanceService;
85 private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
87 private final Set<Runnable> myDeferredSelections = new HashSet<Runnable>();
88 private final Set<Runnable> myDeferredExpansions = new HashSet<Runnable>();
90 private boolean myCanProcessDeferredSelections;
92 private UpdaterTreeState myUpdaterState;
93 private AbstractTreeBuilder myBuilder;
95 private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
96 private long myJanitorPollPeriod = Time.SECOND * 10;
97 private boolean myCheckStructure = false;
100 private boolean myCanYield = false;
102 private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
104 private boolean myYeildingNow;
106 private final Set<DefaultMutableTreeNode> myPendingNodeActions = new HashSet<DefaultMutableTreeNode>();
107 private final Set<Runnable> myYeildingDoneRunnables = new HashSet<Runnable>();
109 private final Alarm myBusyAlarm = new Alarm();
110 private final Runnable myWaiterForReady = new Runnable() {
111 public void run() {
112 maybeSetBusyAndScheduleWaiterForReady(false);
116 private final RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
117 private final RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
118 private final RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimout");
120 private boolean myWasEverIndexNotReady;
121 private boolean myShowing;
122 private FocusAdapter myFocusListener = new FocusAdapter() {
123 @Override
124 public void focusGained(FocusEvent e) {
125 maybeReady();
128 private Set<DefaultMutableTreeNode> myNotForSmartExpand = new HashSet<DefaultMutableTreeNode>();
129 private TreePath myRequestedExpand;
130 private ActionCallback myInitialized = new ActionCallback();
131 private Map<Object, ActionCallback> myReadyCallbacks = new WeakHashMap<Object, ActionCallback>();
133 protected final void init(AbstractTreeBuilder builder,
134 JTree tree,
135 DefaultTreeModel treeModel,
136 AbstractTreeStructure treeStructure,
137 @Nullable Comparator<NodeDescriptor> comparator) {
139 init(builder, tree, treeModel, treeStructure, comparator, true);
142 protected void init(AbstractTreeBuilder builder,
143 JTree tree,
144 DefaultTreeModel treeModel,
145 AbstractTreeStructure treeStructure,
146 @Nullable Comparator<NodeDescriptor> comparator,
147 boolean updateIfInactive) {
148 myBuilder = builder;
149 myTree = tree;
150 myTreeModel = treeModel;
151 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
152 myTree.setModel(myTreeModel);
153 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
154 setTreeStructure(treeStructure);
155 myNodeDescriptorComparator = comparator;
156 myUpdateIfInactive = updateIfInactive;
158 myExpansionListener = new MyExpansionListener();
159 myTree.addTreeExpansionListener(myExpansionListener);
161 mySelectionListener = new MySelectionListener();
162 myTree.addTreeSelectionListener(mySelectionListener);
164 setUpdater(getBuilder().createUpdater());
165 myProgress = getBuilder().createProgressIndicator();
166 Disposer.register(getBuilder(), getUpdater());
168 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
169 public void showNotify() {
170 myShowing = true;
171 myWasEverShown = true;
172 if (!isReleased()) {
173 activate(true);
177 public void hideNotify() {
178 myShowing = false;
179 if (!isReleased()) {
180 deactivate();
184 Disposer.register(getBuilder(), uiNotify);
186 myTree.addFocusListener(myFocusListener);
190 private boolean isNodeActionsPending() {
191 return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty();
194 private void clearNodeActions() {
195 myNodeActions.clear();
196 myNodeChildrenActions.clear();
199 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
200 if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
202 if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
203 final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
204 final boolean isBusy = !isReady() || forcedBusy;
205 if (isBusy && tree.isShowing()) {
206 tree.setPaintBusy(true);
207 myBusyAlarm.cancelAllRequests();
208 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
210 else {
211 tree.setPaintBusy(false);
216 private void initClearanceServiceIfNeeded() {
217 if (ourClearanceService != null) return;
219 ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor");
220 ourClearanceService.scheduleWithFixedDelay(new Runnable() {
221 public void run() {
222 cleanUpAll();
224 }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
227 private void cleanUpAll() {
228 final long now = System.currentTimeMillis();
229 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
230 for (AbstractTreeUi eachUi : uis) {
231 if (eachUi == null) continue;
232 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
233 if (timeToCleanup == null) continue;
234 if (now >= timeToCleanup.longValue()) {
235 ourUi2Countdown.remove(eachUi);
236 getBuilder().cleanUp();
241 protected void doCleanUp() {
242 final Application app = ApplicationManager.getApplication();
243 if (app != null && app.isUnitTestMode()) {
244 cleanUpNow();
246 else {
247 // we are not in EDT
248 //noinspection SSBasedInspection
249 SwingUtilities.invokeLater(new Runnable() {
250 public void run() {
251 if (!isReleased()) {
252 cleanUpNow();
259 private void disposeClearanceService() {
260 try {
261 if (ourClearanceService != null) {
262 ourClearanceService.shutdown();
263 ourClearanceService = null;
266 catch (AccessControlException e) {
267 LOG.warn(e);
271 public void activate(boolean byShowing) {
272 myCanProcessDeferredSelections = true;
273 ourUi2Countdown.remove(this);
275 if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) {
276 getBuilder().updateFromRoot();
279 getUpdater().showNotify();
281 myWasEverShown |= byShowing;
284 public void deactivate() {
285 getUpdater().hideNotify();
286 myBusyAlarm.cancelAllRequests();
288 if (!myWasEverShown) return;
290 if (isNodeActionsPending()) {
291 cancelBackgroundLoading();
292 myUpdateFromRootRequested = true;
295 if (getClearOnHideDelay() >= 0) {
296 ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
297 initClearanceServiceIfNeeded();
302 public void release() {
303 if (isReleased()) return;
305 myTree.removeTreeExpansionListener(myExpansionListener);
306 myTree.removeTreeSelectionListener(mySelectionListener);
307 myTree.removeFocusListener(myFocusListener);
309 disposeNode(getRootNode());
310 myElementToNodeMap.clear();
311 getUpdater().cancelAllRequests();
312 if (myWorker != null) {
313 myWorker.dispose(true);
314 clearWorkerTasks();
316 TREE_NODE_WRAPPER.setValue(null);
317 if (myProgress != null) {
318 myProgress.cancel();
320 disposeClearanceService();
322 myTree = null;
323 setUpdater(null);
324 myWorker = null;
325 //todo [kirillk] afraid to do so just in release day, to uncomment
326 // myTreeStructure = null;
327 myBuilder = null;
329 clearNodeActions();
331 myDeferredSelections.clear();
332 myDeferredExpansions.clear();
333 myYeildingDoneRunnables.clear();
336 public boolean isReleased() {
337 return myBuilder == null;
340 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
341 getTreeStructure().commit();
342 getUpdater().addSubtreeToUpdate(node);
343 getUpdater().performUpdate();
346 public final AbstractTreeStructure getTreeStructure() {
347 return myTreeStructure;
350 public final JTree getTree() {
351 return myTree;
354 @Nullable
355 private NodeDescriptor getDescriptorFrom(DefaultMutableTreeNode node) {
356 return (NodeDescriptor)node.getUserObject();
359 @Nullable
360 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
361 DefaultMutableTreeNode result = null;
362 if (validateAgainstStructure) {
363 int index = 0;
364 while (true) {
365 final DefaultMutableTreeNode node = findNode(element, index);
366 if (node == null) break;
368 if (isNodeValidForElement(element, node)) {
369 result = node;
370 break;
373 index++;
376 else {
377 result = getFirstNode(element);
381 if (result != null && !isNodeInStructure(result)) {
382 disposeNode(result);
383 result = null;
386 return result;
389 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
390 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
393 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
394 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
397 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
398 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
399 final Object parentElement = getElementFor(parent);
400 if (!isInStructure(parentElement)) return false;
402 if (parent instanceof ElementNode) {
403 return ((ElementNode)parent).isValidChild(element);
405 else {
406 for (int i = 0; i < parent.getChildCount(); i++) {
407 final TreeNode child = parent.getChildAt(i);
408 final Object eachElement = getElementFor(child);
409 if (element.equals(eachElement)) return true;
413 return false;
416 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
417 boolean valid = true;
418 while (true) {
419 if (eachParent == null) {
420 valid = eachParentNode == null;
421 break;
424 if (!eachParent.equals(getElementFor(eachParentNode))) {
425 valid = false;
426 break;
429 eachParent = getTreeStructure().getParentElement(eachParent);
430 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
432 return valid;
435 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
436 DefaultMutableTreeNode node = null;
437 for (final Object pathElement : path) {
438 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
439 if (node == null) {
440 break;
443 return node;
446 public final void buildNodeForElement(Object element) {
447 getUpdater().performUpdate();
448 DefaultMutableTreeNode node = getNodeForElement(element, false);
449 if (node == null) {
450 final java.util.List<Object> elements = new ArrayList<Object>();
451 while (true) {
452 element = getTreeStructure().getParentElement(element);
453 if (element == null) {
454 break;
456 elements.add(0, element);
459 for (final Object element1 : elements) {
460 node = getNodeForElement(element1, false);
461 if (node != null) {
462 expand(node, true);
468 public final void buildNodeForPath(Object[] path) {
469 getUpdater().performUpdate();
470 DefaultMutableTreeNode node = null;
471 for (final Object pathElement : path) {
472 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
473 if (node != null && node != path[path.length - 1]) {
474 expand(node, true);
479 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
480 myNodeDescriptorComparator = nodeDescriptorComparator;
481 List<Object> pathsToExpand = new ArrayList<Object>();
482 List<Object> selectionPaths = new ArrayList<Object>();
483 TreeBuilderUtil.storePaths(getBuilder(), getRootNode(), pathsToExpand, selectionPaths, false);
484 resortChildren(getRootNode());
485 myTreeModel.nodeStructureChanged(getRootNode());
486 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
489 protected AbstractTreeBuilder getBuilder() {
490 return myBuilder;
493 private void resortChildren(DefaultMutableTreeNode node) {
494 ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
495 node.removeAllChildren();
496 sortChildren(node, childNodes);
497 for (TreeNode childNode1 : childNodes) {
498 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)childNode1;
499 node.add(childNode);
500 resortChildren(childNode);
504 protected final void initRootNode() {
505 if (myUpdateIfInactive) {
506 activate(false);
507 } else {
508 myUpdateFromRootRequested = true;
512 private void initRootNodeNowIfNeeded(TreeUpdatePass pass) {
513 if (myRootNodeWasInitialized) return;
515 myRootNodeWasInitialized = true;
517 Object rootElement = getTreeStructure().getRootElement();
518 addNodeAction(rootElement, new NodeAction() {
519 public void onReady(final DefaultMutableTreeNode node) {
520 processDeferredActions();
522 }, false);
525 NodeDescriptor rootDescriptor = getTreeStructure().createDescriptor(rootElement, null);
526 getRootNode().setUserObject(rootDescriptor);
527 update(rootDescriptor, false);
528 if (getElementFromDescriptor(rootDescriptor) != null) {
529 createMapping(getElementFromDescriptor(rootDescriptor), getRootNode());
533 insertLoadingNode(getRootNode(), true);
535 boolean willUpdate = false;
536 if (isAutoExpand(rootDescriptor)) {
537 willUpdate = myUnbuiltNodes.contains(getRootNode());
538 expand(getRootNode(), true);
540 if (!willUpdate) {
541 updateNodeChildren(getRootNode(), pass, null, false, isAutoExpand(rootDescriptor), false, true);
543 if (getRootNode().getChildCount() == 0) {
544 myTreeModel.nodeChanged(getRootNode());
548 private boolean isAutoExpand(NodeDescriptor descriptor) {
549 boolean autoExpand = false;
551 if (descriptor != null) {
552 autoExpand = getBuilder().isAutoExpandNode(descriptor);
555 if (!autoExpand && !myTree.isRootVisible()) {
556 Object element = getElementFromDescriptor(descriptor);
557 if (element != null && element.equals(getTreeStructure().getRootElement())) return true;
560 return autoExpand;
563 private boolean isAutoExpand(DefaultMutableTreeNode node) {
564 return isAutoExpand(getDescriptorFrom(node));
567 private boolean update(final NodeDescriptor nodeDescriptor, boolean canBeNonEdt) {
568 if (!canBeNonEdt && myWasEverShown) {
569 assertIsDispatchThread();
572 if (isEdt() || !myWasEverShown || (!isEdt() && canBeNonEdt)) {
573 return getBuilder().updateNodeDescriptor(nodeDescriptor);
575 else {
576 UIUtil.invokeLaterIfNeeded(new Runnable() {
577 public void run() {
578 if (!isReleased()) {
579 getBuilder().updateNodeDescriptor(nodeDescriptor);
583 return true;
587 private void assertIsDispatchThread() {
588 if (isTreeShowing() && !isEdt()) {
589 LOG.error("Must be in event-dispatch thread");
593 private boolean isEdt() {
594 return SwingUtilities.isEventDispatchThread();
597 private boolean isTreeShowing() {
598 return myShowing;
601 private void assertNotDispatchThread() {
602 if (isEdt()) {
603 LOG.error("Must not be in event-dispatch thread");
607 private void processDeferredActions() {
608 processDeferredActions(myDeferredSelections);
609 processDeferredActions(myDeferredExpansions);
612 private void processDeferredActions(Set<Runnable> actions) {
613 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
614 actions.clear();
615 for (Runnable runnable : runnables) {
616 runnable.run();
620 public void doUpdateFromRoot() {
621 updateSubtree(getRootNode(), false);
624 public ActionCallback doUpdateFromRootCB() {
625 final ActionCallback cb = new ActionCallback();
626 getUpdater().runAfterUpdate(new Runnable() {
627 public void run() {
628 cb.setDone();
631 updateSubtree(getRootNode(), false);
632 return cb;
635 public final void updateSubtree(DefaultMutableTreeNode node, boolean canSmartExpand) {
636 updateSubtree(new TreeUpdatePass(node), canSmartExpand);
639 public final void updateSubtree(TreeUpdatePass pass, boolean canSmartExpand) {
640 if (getUpdater() != null) {
641 getUpdater().addSubtreeToUpdate(pass);
643 else {
644 updateSubtreeNow(pass, canSmartExpand);
648 final void updateSubtreeNow(TreeUpdatePass pass, boolean canSmartExpand) {
649 maybeSetBusyAndScheduleWaiterForReady(true);
651 initRootNodeNowIfNeeded(pass);
653 final DefaultMutableTreeNode node = pass.getNode();
655 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
657 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
659 boolean forceUpdate = true;
660 TreePath path = getPathFor(node);
661 boolean invisible = !myTree.isExpanded(path) && (path.getParentPath() == null || !myTree.isExpanded(path.getParentPath()));
663 if (invisible && myUnbuiltNodes.contains(node)) {
664 forceUpdate = false;
667 updateNodeChildren(node, pass, null, false, canSmartExpand, forceUpdate, false);
670 private boolean isToBuildInBackground(NodeDescriptor descriptor) {
671 return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor));
674 @NotNull
675 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
676 final UpdaterTreeState oldState = myUpdaterState;
677 if (oldState == null) {
678 myUpdaterState = state;
679 return state;
681 else {
682 oldState.addAll(state);
683 return oldState;
687 protected void doUpdateNode(DefaultMutableTreeNode node) {
688 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
689 NodeDescriptor descriptor = getDescriptorFrom(node);
690 Object prevElement = getElementFromDescriptor(descriptor);
691 if (prevElement == null) return;
692 boolean changes = update(descriptor, false);
693 if (!isValid(descriptor)) {
694 if (isInStructure(prevElement)) {
695 getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement));
696 return;
699 if (changes) {
700 updateNodeImageAndPosition(node, true);
704 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
705 return getBuilder().getTreeStructureElement(descriptor);
708 private void updateNodeChildren(final DefaultMutableTreeNode node,
709 final TreeUpdatePass pass,
710 @Nullable LoadedChildren loadedChildren,
711 boolean forcedNow,
712 final boolean toSmartExpand,
713 boolean forceUpdate,
714 final boolean descriptorIsUpToDate) {
715 getTreeStructure().commit();
716 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node);
717 final boolean wasLeaf = node.getChildCount() == 0;
719 try {
720 final NodeDescriptor descriptor = getDescriptorFrom(node);
721 if (descriptor == null) {
722 removeLoading(node, true);
723 return;
726 boolean bgBuild = isToBuildInBackground(descriptor);
727 boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded && !forceUpdate;
728 LoadedChildren preloaded = loadedChildren;
729 boolean descriptorWasUpdated = descriptorIsUpToDate;
731 if (notRequiredToUpdateChildren) {
732 if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) {
733 insertLoadingNode(node, true);
735 return;
738 if (!forcedNow) {
739 if (!bgBuild) {
740 if (myUnbuiltNodes.contains(node)) {
741 if (!descriptorWasUpdated) {
742 update(descriptor, false);
743 descriptorWasUpdated = true;
745 Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null);
746 if (unbuilt.getFirst()) return;
747 preloaded = unbuilt.getSecond();
753 boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded);
755 if (!forcedNow && isToBuildInBackground(descriptor)) {
756 queueBackgroundUpdate(node, descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorWasUpdated);
757 return;
758 } else {
759 if (!descriptorWasUpdated) {
760 update(descriptor, false);
763 updateNodeChildrenNow(node, pass, preloaded, toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
766 finally {
767 processNodeActionsIfReady(node);
771 private boolean isChildNodeForceUpdate(DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) {
772 TreePath path = getPathFor(node);
773 return parentForceUpdate && (parentExpanded || myTree.isExpanded(path));
776 private void updateNodeChildrenNow(final DefaultMutableTreeNode node, final TreeUpdatePass pass,
777 final LoadedChildren preloadedChildren,
778 final boolean toSmartExpand,
779 final boolean wasExpanded,
780 final boolean wasLeaf,
781 final boolean forceUpdate) {
782 final NodeDescriptor descriptor = getDescriptorFrom(node);
784 final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren);
785 final LoadedChildren loadedChildren = preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray());
788 addToUpdating(node);
789 pass.setCurrentNode(node);
791 final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
793 processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren).doWhenDone(new Runnable() {
794 public void run() {
795 if (isDisposed(node)) {
796 return;
799 removeLoading(node, false);
801 final boolean expanded = isExpanded(node, wasExpanded);
803 ArrayList<TreeNode> nodesToInsert = collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren);
804 insertNodesInto(nodesToInsert, node);
805 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
806 removeLoading(node, true);
808 if (node.getChildCount() > 0) {
809 if (expanded) {
810 expand(node, canSmartExpand);
814 removeFromUpdating(node);
816 final Object element = getElementFor(node);
817 addNodeAction(element, new NodeAction() {
818 public void onReady(final DefaultMutableTreeNode node) {
819 removeLoading(node, false);
821 }, false);
823 processNodeActionsIfReady(node);
828 private boolean isDisposed(DefaultMutableTreeNode node) {
829 return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
832 private void expand(DefaultMutableTreeNode node, boolean canSmartExpand) {
833 expand(new TreePath(node.getPath()), canSmartExpand);
836 private void expand(final TreePath path, boolean canSmartExpand) {
837 if (path == null) return;
840 final Object last = path.getLastPathComponent();
841 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
842 final boolean isRoot = last == myTree.getModel().getRoot();
843 final TreePath parent = path.getParentPath();
844 if (isRoot && !myTree.isExpanded(path)) {
845 if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) {
846 insertLoadingNode((DefaultMutableTreeNode)last, false);
848 expandPath(path, canSmartExpand);
850 else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last))) {
851 if (last instanceof DefaultMutableTreeNode) {
852 processNodeActionsIfReady((DefaultMutableTreeNode)last);
855 else {
856 if (isLeaf && myUnbuiltNodes.contains(last)) {
857 insertLoadingNode((DefaultMutableTreeNode)last, true);
858 expandPath(path, canSmartExpand);
860 else if (isLeaf && parent != null) {
861 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
862 if (parentNode != null) {
863 addToUnbuilt(parentNode);
865 expandPath(parent, canSmartExpand);
867 else {
868 expandPath(path, canSmartExpand);
873 private void addToUnbuilt(DefaultMutableTreeNode node) {
874 myUnbuiltNodes.add(node);
877 private void removeFromUnbuilt(DefaultMutableTreeNode node) {
878 myUnbuiltNodes.remove(node);
881 private Pair<Boolean, LoadedChildren> processUnbuilt(final DefaultMutableTreeNode node,
882 final NodeDescriptor descriptor,
883 final TreeUpdatePass pass,
884 boolean isExpanded,
885 final LoadedChildren loadedChildren) {
886 if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) {
887 return new Pair<Boolean, LoadedChildren>(true, null);
890 final Object element = getElementFor(node);
892 final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element));
894 boolean processed;
896 if (children.getElements().size() == 0) {
897 removeLoading(node, true);
898 processed = true;
900 else {
901 if (isAutoExpand(node)) {
902 addNodeAction(getElementFor(node), new NodeAction() {
903 public void onReady(final DefaultMutableTreeNode node) {
904 final TreePath path = new TreePath(node.getPath());
905 if (getTree().isExpanded(path) || children.getElements().size() == 0) {
906 removeLoading(node, false);
908 else {
909 maybeYeild(new ActiveRunnable() {
910 public ActionCallback run() {
911 expand(element, null);
912 return new ActionCallback.Done();
914 }, pass, node);
917 }, false);
919 processed = false;
922 processNodeActionsIfReady(node);
924 return new Pair<Boolean, LoadedChildren>(processed, children);
927 private boolean removeIfLoading(TreeNode node) {
928 if (isLoadingNode(node)) {
929 moveSelectionToParentIfNeeded(node);
930 removeNodeFromParent((MutableTreeNode)node, false);
931 return true;
934 return false;
937 private void moveSelectionToParentIfNeeded(TreeNode node) {
938 TreePath path = getPathFor(node);
939 if (myTree.getSelectionModel().isPathSelected(path)) {
940 TreePath parentPath = path.getParentPath();
941 myTree.getSelectionModel().removeSelectionPath(path);
942 if (parentPath != null) {
943 myTree.getSelectionModel().addSelectionPath(parentPath);
948 //todo [kirillk] temporary consistency check
949 private Object[] getChildrenFor(final Object element) {
950 final Object[] passOne;
951 try {
952 passOne = getTreeStructure().getChildElements(element);
954 catch (IndexNotReadyException e) {
955 if (!myWasEverIndexNotReady) {
956 myWasEverIndexNotReady = true;
957 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
959 return ArrayUtil.EMPTY_OBJECT_ARRAY;
962 if (!myCheckStructure) return passOne;
964 final Object[] passTwo = getTreeStructure().getChildElements(element);
966 final HashSet two = new HashSet(Arrays.asList(passTwo));
968 if (passOne.length != passTwo.length) {
969 LOG.error(
970 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
971 element);
973 else {
974 for (Object eachInOne : passOne) {
975 if (!two.contains(eachInOne)) {
976 LOG.error(
977 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
978 element);
979 break;
984 return passOne;
987 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert, TreeUpdatePass pass, boolean canSmartExpand, boolean forceUpdate) {
988 for (TreeNode aNodesToInsert : nodesToInsert) {
989 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
990 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
994 private ActionCallback processExistingNodes(final DefaultMutableTreeNode node,
995 final MutualMap<Object, Integer> elementToIndexMap,
996 final TreeUpdatePass pass,
997 final boolean canSmartExpand,
998 final boolean forceUpdate,
999 final boolean wasExpaned,
1000 final LoadedChildren preloaded) {
1002 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
1003 return maybeYeild(new ActiveRunnable() {
1004 public ActionCallback run() {
1005 if (pass.isExpired()) return new ActionCallback.Rejected();
1006 if (childNodes.size() == 0) return new ActionCallback.Done();
1009 final ActionCallback result = new ActionCallback(childNodes.size());
1011 for (TreeNode each : childNodes) {
1012 final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each;
1013 if (isLoadingNode(eachChild)) {
1014 result.setDone();
1015 continue;
1018 final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned);
1020 maybeYeild(new ActiveRunnable() {
1021 @Override
1022 public ActionCallback run() {
1023 return processExistingNode(eachChild, getDescriptorFrom(eachChild), node, elementToIndexMap, pass, canSmartExpand, childForceUpdate, preloaded);
1025 }, pass, node).notify(result);
1027 if (result.isRejected()) {
1028 break;
1032 return result;
1034 }, pass, node);
1037 private boolean isRerunNeeded(TreeUpdatePass pass) {
1038 if (pass.isExpired()) return false;
1040 final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
1042 return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
1045 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
1046 final ActionCallback result = new ActionCallback();
1048 if (isRerunNeeded(pass)) {
1049 getUpdater().addSubtreeToUpdate(pass);
1050 result.setRejected();
1052 else {
1053 if (isToYieldUpdateFor(node)) {
1054 pass.setCurrentNode(node);
1055 yieldAndRun(new Runnable() {
1056 public void run() {
1057 if (pass.isExpired()) return;
1059 if (isRerunNeeded(pass)) {
1060 runDone(new Runnable() {
1061 public void run() {
1062 if (!pass.isExpired()) {
1063 getUpdater().addSubtreeToUpdate(pass);
1067 result.setRejected();
1069 else {
1070 processRunnable.run().notify(result);
1073 }, pass);
1075 else {
1076 processRunnable.run().notify(result);
1080 return result;
1083 private void yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
1084 myYeildingPasses.add(pass);
1085 myYeildingNow = true;
1086 yield(new Runnable() {
1087 public void run() {
1088 if (isReleased()) {
1089 return;
1092 runOnYieldingDone(new Runnable() {
1093 public void run() {
1094 if (isReleased()) {
1095 return;
1097 executeYieldingRequest(runnable, pass);
1104 public boolean isYeildingNow() {
1105 return myYeildingNow;
1108 private boolean hasSheduledUpdates() {
1109 return getUpdater().hasNodesToUpdate() || isLoadingInBackgroundNow();
1112 public boolean isReady() {
1113 return isIdle() && !hasPendingWork();
1116 public boolean hasPendingWork() {
1117 return hasNodesToUpdate() || (myUpdaterState != null && myUpdaterState.isProcessingNow());
1120 public boolean isIdle() {
1121 return !isYeildingNow() && !isWorkerBusy() && (!hasSheduledUpdates() || getUpdater().isInPostponeMode());
1124 private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
1125 try {
1126 myYeildingPasses.remove(pass);
1127 runnable.run();
1129 finally {
1130 maybeYeildingFinished();
1134 private void maybeYeildingFinished() {
1135 if (myYeildingPasses.size() == 0) {
1136 myYeildingNow = false;
1137 flushPendingNodeActions();
1141 void maybeReady() {
1142 if (isReleased()) return;
1144 if (isReady()) {
1145 if (myTree.isShowing() || myUpdateIfInactive) {
1146 myInitialized.setDone();
1149 if (myTree.isShowing()) {
1150 if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
1151 TreeUtil.ensureSelection(myTree);
1155 if (myInitialized.isDone()) {
1156 for (ActionCallback each : getReadyCallbacks(true)) {
1157 each.setDone();
1163 private void flushPendingNodeActions() {
1164 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
1165 myPendingNodeActions.clear();
1167 for (DefaultMutableTreeNode each : nodes) {
1168 processNodeActionsIfReady(each);
1171 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
1172 for (Runnable each : actions) {
1173 if (!isYeildingNow()) {
1174 myYeildingDoneRunnables.remove(each);
1175 each.run();
1179 maybeReady();
1182 protected void runOnYieldingDone(Runnable onDone) {
1183 getBuilder().runOnYeildingDone(onDone);
1186 protected void yield(Runnable runnable) {
1187 getBuilder().yield(runnable);
1190 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
1191 if (!canYield()) return false;
1192 return getBuilder().isToYieldUpdateFor(node);
1195 private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor, @Nullable LoadedChildren preloadedChildren) {
1196 MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
1197 List children = preloadedChildren != null ? preloadedChildren.getElements() : Arrays.asList(getChildrenFor(getBuilder().getTreeStructureElement(descriptor)));
1198 int index = 0;
1199 for (Object child : children) {
1200 if (!isValid(child)) continue;
1201 elementToIndexMap.put(child, Integer.valueOf(index));
1202 index++;
1204 return elementToIndexMap;
1207 private void expand(final DefaultMutableTreeNode node,
1208 final NodeDescriptor descriptor,
1209 final boolean wasLeaf,
1210 final boolean canSmartExpand) {
1211 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1212 alarm.addRequest(new Runnable() {
1213 public void run() {
1214 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1216 }, WAIT_CURSOR_DELAY);
1218 if (wasLeaf && isAutoExpand(descriptor)) {
1219 expand(node, canSmartExpand);
1222 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1223 for (TreeNode node1 : nodes) {
1224 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1225 if (isLoadingNode(childNode)) continue;
1226 NodeDescriptor childDescr = getDescriptorFrom(childNode);
1227 if (isAutoExpand(childDescr)) {
1228 addNodeAction(getElementFor(childNode), new NodeAction() {
1229 public void onReady(DefaultMutableTreeNode node) {
1230 expand(childNode, canSmartExpand);
1232 }, false);
1233 addSubtreeToUpdate(childNode);
1237 int n = alarm.cancelAllRequests();
1238 if (n == 0) {
1239 myTree.setCursor(Cursor.getDefaultCursor());
1243 public static boolean isLoadingNode(final Object node) {
1244 return node instanceof LoadingNode;
1247 private ArrayList<TreeNode> collectNodesToInsert(final NodeDescriptor descriptor, final MutualMap<Object, Integer> elementToIndexMap, DefaultMutableTreeNode parent, boolean addLoadingNode, @NotNull LoadedChildren loadedChildren) {
1248 ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1249 final Collection<Object> allElements = elementToIndexMap.getKeys();
1251 for (Object child : allElements) {
1252 Integer index = elementToIndexMap.getValue(child);
1253 NodeDescriptor childDescr = loadedChildren.getDescriptor(child);
1254 boolean needToUpdate = false;
1255 if (childDescr == null) {
1256 childDescr = getTreeStructure().createDescriptor(child, descriptor);
1257 needToUpdate = true;
1260 //noinspection ConstantConditions
1261 if (childDescr == null) {
1262 LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1263 continue;
1265 childDescr.setIndex(index.intValue());
1267 if (needToUpdate) {
1268 loadedChildren.putDescriptor(child, childDescr, update(childDescr, false));
1271 Object element = getElementFromDescriptor(childDescr);
1272 if (element == null) {
1273 LOG.error("childDescr.getElement() == null, child = " + child + ", builder = " + this);
1274 continue;
1277 DefaultMutableTreeNode node = getNodeForElement(element, false);
1278 if (node == null || node.getParent() != parent) {
1279 final DefaultMutableTreeNode childNode = createChildNode(childDescr);
1280 if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr)) {
1281 insertLoadingNode(childNode, true);
1282 } else {
1283 addToUnbuilt(childNode);
1285 nodesToInsert.add(childNode);
1286 createMapping(element, childNode);
1290 return nodesToInsert;
1293 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1294 return new ElementNode(this, descriptor);
1297 protected boolean canYield() {
1298 return myCanYield && myYeildingUpdate.asBoolean();
1301 public long getClearOnHideDelay() {
1302 return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
1305 public ActionCallback getInitialized() {
1306 return myInitialized;
1309 public ActionCallback getReady(Object requestor) {
1310 if (isReady()) {
1311 return new ActionCallback.Done();
1312 } else {
1313 return addReadyCallback(requestor);
1317 private void addToUpdating(DefaultMutableTreeNode node) {
1318 synchronized (myUpdatingChildren) {
1319 myUpdatingChildren.add(node);
1323 private void removeFromUpdating(DefaultMutableTreeNode node) {
1324 synchronized (myUpdatingChildren) {
1325 myUpdatingChildren.remove(node);
1329 public boolean isUpdatingNow(DefaultMutableTreeNode node) {
1330 synchronized (myUpdatingChildren) {
1331 return myUpdatingChildren.contains(node);
1335 boolean hasUpdatingNow() {
1336 synchronized (myUpdatingChildren) {
1337 return myUpdatingChildren.size() > 0;
1341 public Map getNodeActions() {
1342 return myNodeActions;
1345 public List<Object> getLoadedChildrenFor(Object element) {
1346 List<Object> result = new ArrayList<Object>();
1348 DefaultMutableTreeNode node = (DefaultMutableTreeNode)findNodeByElement(element);
1349 if (node != null) {
1350 for (int i = 0; i < node.getChildCount(); i++) {
1351 TreeNode each = node.getChildAt(i);
1352 if (isLoadingNode(each)) continue;
1354 result.add(getElementFor(each));
1358 return result;
1361 public boolean hasNodesToUpdate() {
1362 return getUpdater().hasNodesToUpdate() || hasUpdatingNow() || isLoadingInBackgroundNow();
1365 public List<Object> getExpandedElements() {
1366 List<Object> result = new ArrayList<Object>();
1367 Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode()));
1368 while (enumeration.hasMoreElements()) {
1369 TreePath each = enumeration.nextElement();
1370 Object eachElement = getElementFor(each.getLastPathComponent());
1371 if (eachElement != null) {
1372 result.add(eachElement);
1376 return result;
1379 static class ElementNode extends DefaultMutableTreeNode {
1381 Set<Object> myElements = new HashSet<Object>();
1382 AbstractTreeUi myUi;
1384 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1385 super(descriptor);
1386 myUi = ui;
1389 @Override
1390 public void insert(final MutableTreeNode newChild, final int childIndex) {
1391 super.insert(newChild, childIndex);
1392 final Object element = myUi.getElementFor(newChild);
1393 if (element != null) {
1394 myElements.add(element);
1398 @Override
1399 public void remove(final int childIndex) {
1400 final TreeNode node = getChildAt(childIndex);
1401 super.remove(childIndex);
1402 final Object element = myUi.getElementFor(node);
1403 if (element != null) {
1404 myElements.remove(element);
1408 boolean isValidChild(Object childElement) {
1409 return myElements.contains(childElement);
1412 @Override
1413 public String toString() {
1414 return String.valueOf(getUserObject());
1418 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1419 DefaultMutableTreeNode eachParent = kid;
1420 while (eachParent != null) {
1421 if (isUpdatingNow(eachParent)) return true;
1422 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1425 return false;
1428 private boolean isLoadedInBackground(Object element) {
1429 synchronized (myLoadingParents) {
1430 return myLoadingParents.contains(element);
1434 private void addToLoadedInBackground(Object element) {
1435 synchronized (myLoadingParents) {
1436 myLoadingParents.add(element);
1440 private void removeFromLoadedInBackground(final Object element) {
1441 synchronized (myLoadingParents) {
1442 myLoadingParents.remove(element);
1446 private boolean isLoadingInBackgroundNow() {
1447 synchronized (myLoadingParents) {
1448 return myLoadingParents.size() > 0;
1452 private boolean queueBackgroundUpdate(final DefaultMutableTreeNode node,
1453 final NodeDescriptor descriptor,
1454 final TreeUpdatePass pass,
1455 final boolean canSmartExpand,
1456 final boolean wasExpanded,
1457 final boolean forceUpdate,
1458 final boolean descriptorIsUpToDate) {
1459 assertIsDispatchThread();
1461 final Object oldElementFromDescriptor = getElementFromDescriptor(descriptor);
1463 if (isLoadedInBackground(oldElementFromDescriptor)) return false;
1465 addToLoadedInBackground(oldElementFromDescriptor);
1467 if (!isNodeBeingBuilt(node)) {
1468 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1469 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1472 final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
1473 final Ref<Object> elementFromDescriptor = new Ref<Object>();
1474 Runnable buildRunnable = new Runnable() {
1475 public void run() {
1476 if (isReleased()) {
1477 return;
1480 if (!descriptorIsUpToDate) {
1481 update(descriptor, true);
1484 Object element = getElementFromDescriptor(descriptor);
1485 if (element == null) {
1486 removeFromLoadedInBackground(oldElementFromDescriptor);
1487 return;
1490 elementFromDescriptor.set(element);
1492 Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(descriptor));
1493 LoadedChildren loaded = new LoadedChildren(loadedElements);
1494 for (Object each : loadedElements) {
1495 NodeDescriptor eachChildDescriptor = getTreeStructure().createDescriptor(each, descriptor);
1496 loaded.putDescriptor(each, eachChildDescriptor, eachChildDescriptor.update());
1499 children.set(loaded);
1503 final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
1504 Runnable updateRunnable = new Runnable() {
1505 public void run() {
1506 if (isReleased()) return;
1507 if (children.get() == null) return;
1509 if (isRerunNeeded(pass)) {
1510 removeFromLoadedInBackground(elementFromDescriptor.get());
1511 getUpdater().addSubtreeToUpdate(pass);
1512 return;
1515 removeFromLoadedInBackground(elementFromDescriptor.get());
1517 if (myUnbuiltNodes.contains(node)) {
1518 Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, isExpanded(node, wasExpanded), children.get());
1519 if (unbuilt.getFirst()) {
1520 nodeToProcessActions[0] = node;
1521 return;
1525 updateNodeChildren(node, pass, children.get(), true, canSmartExpand, forceUpdate, true);
1528 if (isRerunNeeded(pass)) {
1529 getUpdater().addSubtreeToUpdate(pass);
1530 return;
1533 Object element = elementFromDescriptor.get();
1535 if (element != null) {
1536 removeLoading(node, true);
1537 nodeToProcessActions[0] = node;
1541 addTaskToWorker(buildRunnable, true, updateRunnable, new Runnable() {
1542 public void run() {
1543 if (nodeToProcessActions[0] != null) {
1544 processNodeActionsIfReady(nodeToProcessActions[0]);
1548 return true;
1551 private boolean isExpanded(DefaultMutableTreeNode node, boolean isExpanded) {
1552 return isExpanded || myTree.isExpanded(getPathFor(node));
1555 private void removeLoading(DefaultMutableTreeNode parent, boolean removeFromUnbuilt) {
1556 for (int i = 0; i < parent.getChildCount(); i++) {
1557 TreeNode child = parent.getChildAt(i);
1558 if (removeIfLoading(child)) {
1559 i--;
1563 if (removeFromUnbuilt) {
1564 removeFromUnbuilt(parent);
1567 if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
1568 insertLoadingNode(parent, false);
1571 maybeReady();
1574 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1575 if (isNodeBeingBuilt(node)) return;
1577 final Object o = node.getUserObject();
1578 if (!(o instanceof NodeDescriptor)) return;
1581 if (isYeildingNow()) {
1582 myPendingNodeActions.add(node);
1583 return;
1586 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1588 processActions(node, element, myNodeActions);
1590 boolean childrenReady = !isLoadedInBackground(element);
1591 if (childrenReady) {
1592 processActions(node, element, myNodeChildrenActions);
1595 if (!isUpdatingParent(node) && !isWorkerBusy()) {
1596 final UpdaterTreeState state = myUpdaterState;
1597 if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1598 if (!state.restore(childrenReady ? node : null)) {
1599 setUpdaterState(state);
1604 maybeReady();
1608 private void processActions(DefaultMutableTreeNode node, Object element, final Map<Object, List<NodeAction>> nodeActions) {
1609 final List<NodeAction> actions = nodeActions.get(element);
1610 if (actions != null) {
1611 nodeActions.remove(element);
1612 for (NodeAction each : actions) {
1613 each.onReady(node);
1619 private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1620 return !myNotForSmartExpand.contains(node) && canSmartExpand;
1623 private void processSmartExpand(final DefaultMutableTreeNode node, final boolean canSmartExpand) {
1624 if (!getBuilder().isSmartExpand() || !canSmartExpand(node, canSmartExpand)) return;
1626 if (isNodeBeingBuilt(node)) {
1627 addNodeAction(getElementFor(node), new NodeAction() {
1628 public void onReady(DefaultMutableTreeNode node) {
1629 processSmartExpand(node, canSmartExpand);
1631 }, true);
1633 else {
1634 TreeNode child = getChildForSmartExpand(node);
1635 if (child != null) {
1636 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child);
1637 myTree.expandPath(childPath);
1642 @Nullable
1643 private TreeNode getChildForSmartExpand(DefaultMutableTreeNode node) {
1644 int realChildCount = 0;
1645 TreeNode nodeToExpand = null;
1647 for (int i = 0; i < node.getChildCount(); i++) {
1648 TreeNode eachChild = node.getChildAt(i);
1650 if (!isLoadingNode(eachChild)) {
1651 realChildCount++;
1652 if (nodeToExpand == null) {
1653 nodeToExpand = eachChild;
1657 if (realChildCount > 1) {
1658 nodeToExpand = null;
1659 break;
1663 return nodeToExpand;
1666 public boolean isLoadingChildrenFor(final Object nodeObject) {
1667 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1669 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1671 int loadingNodes = 0;
1672 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
1673 TreeNode child = node.getChildAt(i);
1674 if (isLoadingNode(child)) {
1675 loadingNodes++;
1678 return loadingNodes > 0 && loadingNodes == node.getChildCount();
1681 private boolean isParentLoading(Object nodeObject) {
1682 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1684 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1686 TreeNode eachParent = node.getParent();
1688 while (eachParent != null) {
1689 eachParent = eachParent.getParent();
1690 if (eachParent instanceof DefaultMutableTreeNode) {
1691 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
1692 if (isLoadedInBackground(eachElement)) return true;
1696 return false;
1699 protected String getLoadingNodeText() {
1700 return IdeBundle.message("progress.searching");
1703 private ActionCallback processExistingNode(final DefaultMutableTreeNode childNode,
1704 final NodeDescriptor childDescriptor,
1705 final DefaultMutableTreeNode parentNode,
1706 final MutualMap<Object, Integer> elementToIndexMap,
1707 TreeUpdatePass pass,
1708 final boolean canSmartExpand,
1709 boolean forceUpdate,
1710 LoadedChildren parentPreloadedChildren) {
1712 if (pass.isExpired()) {
1713 return new ActionCallback.Rejected();
1716 NodeDescriptor childDesc = childDescriptor;
1719 if (childDesc == null) {
1720 pass.expire();
1721 return new ActionCallback.Rejected();
1723 Object oldElement = getElementFromDescriptor(childDesc);
1724 if (oldElement == null) {
1725 pass.expire();
1726 return new ActionCallback.Rejected();
1729 boolean changes;
1730 if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) {
1731 changes = parentPreloadedChildren.isUpdated(oldElement);
1732 } else {
1733 changes = update(childDesc, false);
1736 boolean forceRemapping = false;
1737 Object newElement = getElementFromDescriptor(childDesc);
1739 Integer index = newElement != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc)) : null;
1740 if (index != null) {
1741 final Object elementFromMap = elementToIndexMap.getKey(index);
1742 if (elementFromMap != newElement && elementFromMap.equals(newElement)) {
1743 if (isInStructure(elementFromMap) && isInStructure(newElement)) {
1744 if (parentNode.getUserObject() instanceof NodeDescriptor) {
1745 final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
1746 childDesc = getTreeStructure().createDescriptor(elementFromMap, parentDescriptor);
1747 childNode.setUserObject(childDesc);
1748 newElement = elementFromMap;
1749 forceRemapping = true;
1750 update(childDesc, false);
1751 changes = true;
1756 if (childDesc.getIndex() != index.intValue()) {
1757 changes = true;
1759 childDesc.setIndex(index.intValue());
1762 if (index != null && changes) {
1763 updateNodeImageAndPosition(childNode, false);
1765 if (!oldElement.equals(newElement) | forceRemapping) {
1766 removeMapping(oldElement, childNode, newElement);
1767 if (newElement != null) {
1768 createMapping(newElement, childNode);
1772 if (index == null) {
1773 int selectedIndex = -1;
1774 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
1775 selectedIndex = parentNode.getIndex(childNode);
1778 if (childNode.getParent() instanceof DefaultMutableTreeNode) {
1779 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
1780 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
1781 if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
1782 insertLoadingNode(parent, false);
1787 Object disposedElement = getElementFor(childNode);
1789 removeNodeFromParent(childNode, selectedIndex >= 0);
1790 disposeNode(childNode);
1792 adjustSelectionOnChildRemove(parentNode, selectedIndex, disposedElement);
1794 else {
1795 elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc));
1796 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
1799 if (parentNode.equals(getRootNode())) {
1800 myTreeModel.nodeChanged(getRootNode());
1803 return new ActionCallback.Done();
1806 private void adjustSelectionOnChildRemove(DefaultMutableTreeNode parentNode, int selectedIndex, Object disposedElement) {
1807 DefaultMutableTreeNode node = getNodeForElement(disposedElement, false);
1808 if (node != null && isValidForSelectionAdjusting(node)) {
1809 Object newElement = getElementFor(node);
1810 addSelectionPath(getPathFor(node), true, getExpiredElementCondition(newElement));
1811 return;
1815 if (selectedIndex >= 0) {
1816 if (parentNode.getChildCount() > 0) {
1817 if (parentNode.getChildCount() > selectedIndex) {
1818 TreeNode newChildNode = parentNode.getChildAt(selectedIndex);
1819 if (isValidForSelectionAdjusting(newChildNode)) {
1820 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement));
1823 else {
1824 TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1);
1825 if (isValidForSelectionAdjusting(newChild)) {
1826 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement));
1830 else {
1831 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement));
1836 private boolean isValidForSelectionAdjusting(TreeNode node) {
1837 if (!myTree.isRootVisible() && getRootNode() == node) return false;
1839 if (isLoadingNode(node)) return true;
1841 final Object elementInTree = getElementFor(node);
1842 if (elementInTree == null) return false;
1844 final TreeNode parentNode = node.getParent();
1845 final Object parentElementInTree = getElementFor(parentNode);
1846 if (parentElementInTree == null) return false;
1848 final Object parentElement = getTreeStructure().getParentElement(elementInTree);
1850 return parentElementInTree.equals(parentElement);
1853 public Condition getExpiredElementCondition(final Object element) {
1854 return new Condition() {
1855 public boolean value(final Object o) {
1856 return isInStructure(element);
1861 private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement) {
1862 doWithUpdaterState(new Runnable() {
1863 public void run() {
1864 TreePath toSelect = null;
1866 if (isLoadingNode(path.getLastPathComponent())) {
1867 final TreePath parentPath = path.getParentPath();
1868 if (parentPath != null) {
1869 toSelect = parentPath;
1872 else {
1873 toSelect = path;
1876 if (toSelect != null) {
1877 myTree.addSelectionPath(toSelect);
1879 if (isAdjustedSelection && myUpdaterState != null) {
1880 final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
1881 myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement);
1888 private static TreePath getPathFor(TreeNode node) {
1889 if (node instanceof DefaultMutableTreeNode) {
1890 return new TreePath(((DefaultMutableTreeNode)node).getPath());
1892 else {
1893 ArrayList nodes = new ArrayList();
1894 TreeNode eachParent = node;
1895 while (eachParent != null) {
1896 nodes.add(eachParent);
1897 eachParent = eachParent.getParent();
1900 return new TreePath(ArrayUtil.toObjectArray(nodes));
1905 private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
1906 doWithUpdaterState(new Runnable() {
1907 public void run() {
1908 if (willAdjustSelection) {
1909 final TreePath path = getPathFor(node);
1910 if (myTree.isPathSelected(path)) {
1911 myTree.removeSelectionPath(path);
1915 myTreeModel.removeNodeFromParent(node);
1920 private void expandPath(final TreePath path, final boolean canSmartExpand) {
1921 doWithUpdaterState(new Runnable() {
1922 public void run() {
1923 if (path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
1924 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
1925 if (node.getChildCount() > 0 && !myTree.isExpanded(path)) {
1926 if (!canSmartExpand) {
1927 myNotForSmartExpand.add(node);
1929 try {
1930 myRequestedExpand = path;
1931 myTree.expandPath(path);
1932 processSmartExpand(node, canSmartExpand);
1934 finally {
1935 myNotForSmartExpand.remove(node);
1936 myRequestedExpand = null;
1938 } else {
1939 processNodeActionsIfReady(node);
1946 private void doWithUpdaterState(Runnable runnable) {
1947 if (myUpdaterState != null) {
1948 myUpdaterState.process(runnable);
1950 else {
1951 runnable.run();
1955 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
1956 return descriptor.update();
1959 private void makeLoadingOrLeafIfNoChildren(final DefaultMutableTreeNode node) {
1960 //Object[] children = null;
1961 //final boolean[] hasNoChildren = new boolean[1];
1962 //final NodeDescriptor descriptor = getDescriptorFrom(node);
1963 //if (!getBuilder().isAlwaysShowPlus(descriptor)) {
1964 // Runnable updateRunnable = new Runnable() {
1965 // public void run() {
1966 // if (isReleased()) return;
1968 // if (hasNoChildren[0]) {
1969 // //update(descriptor, false);
1970 // removeLoading(node, false);
1971 // }
1972 // }
1973 // };
1975 // if (isToBuildInBackground(descriptor)) {
1976 // //Runnable buildRunnable = new Runnable() {
1977 // // public void run() {
1978 // // if (isReleased()) return;
1979 // //
1980 // // update(descriptor, true);
1981 // // Object element = getBuilder().getTreeStructureElement(descriptor);
1982 // // if (element == null && !isValid(element)) return;
1983 // //
1984 // // Object[] children = getChildrenFor(element);
1985 // // hasNoChildren[0] = children.length == 0;
1986 // // }
1987 // //};
1988 // //addTaskToWorker(buildRunnable, false, updateRunnable, new Runnable() {
1989 // // public void run() {
1990 // // processNodeActionsIfReady(node);
1991 // // }
1992 // //});
1993 // }
1994 // else {
1995 // children = getChildrenFor(getBuilder().getTreeStructureElement(descriptor));
1996 // if (children.length == 0) return children;
1997 // }
2000 //insertLoadingNode(node, true);
2002 TreePath path = getPathFor(node);
2003 if (path == null) return;
2005 insertLoadingNode(node, true);
2007 final NodeDescriptor descriptor = getDescriptorFrom(node);
2008 if (getBuilder().isAlwaysShowPlus(descriptor)) return;
2011 TreePath parentPath = path.getParentPath();
2012 if (myTree.isVisible(path) || (parentPath != null && myTree.isExpanded(parentPath))) {
2013 if (myTree.isExpanded(path)) {
2014 getUpdater().addSubtreeToUpdate(node);
2015 } else {
2016 insertLoadingNode(node, false);
2022 private boolean isValid(DefaultMutableTreeNode node) {
2023 if (node == null) return false;
2024 final Object object = node.getUserObject();
2025 if (object instanceof NodeDescriptor) {
2026 return isValid((NodeDescriptor)object);
2029 return false;
2032 private boolean isValid(NodeDescriptor descriptor) {
2033 if (descriptor == null) return false;
2034 return isValid(getElementFromDescriptor(descriptor));
2037 private boolean isValid(Object element) {
2038 if (element instanceof ValidateableNode) {
2039 if (!((ValidateableNode)element).isValid()) return false;
2041 return getBuilder().validateNode(element);
2044 private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
2045 if (!isLoadingChildrenFor(node)) {
2046 myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
2049 if (addToUnbuilt) {
2050 addToUnbuilt(node);
2055 protected void addTaskToWorker(@NotNull final Runnable bgReadActionRunnable,
2056 boolean first,
2057 @Nullable final Runnable edtPostRunnable,
2058 @Nullable final Runnable finalizeEdtRunnable) {
2059 registerWorkerTask(bgReadActionRunnable);
2061 final Runnable pooledThreadWithProgressRunnable = new Runnable() {
2062 public void run() {
2063 if (isReleased()) {
2064 return;
2067 final AbstractTreeBuilder builder = getBuilder();
2069 builder.runBackgroundLoading(new Runnable() {
2070 public void run() {
2071 assertNotDispatchThread();
2073 if (isReleased()) {
2074 return;
2077 try {
2078 bgReadActionRunnable.run();
2080 if (edtPostRunnable != null && !isReleased()) {
2081 builder.updateAfterLoadedInBackground(new Runnable() {
2082 public void run() {
2083 try {
2084 assertIsDispatchThread();
2086 if (isReleased()) {
2087 return;
2090 edtPostRunnable.run();
2092 finally {
2093 unregisterWorkerTask(bgReadActionRunnable, finalizeEdtRunnable);
2098 else {
2099 unregisterWorkerTask(bgReadActionRunnable, finalizeEdtRunnable);
2102 catch (ProcessCanceledException e) {
2103 unregisterWorkerTask(bgReadActionRunnable, finalizeEdtRunnable);
2105 catch (Throwable t) {
2106 unregisterWorkerTask(bgReadActionRunnable, finalizeEdtRunnable);
2107 throw new RuntimeException(t);
2114 Runnable pooledThreadRunnable = new Runnable() {
2115 public void run() {
2116 if (isReleased()) return;
2118 try {
2119 if (myProgress != null) {
2120 ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress);
2122 else {
2123 pooledThreadWithProgressRunnable.run();
2126 catch (ProcessCanceledException e) {
2127 //ignore
2132 if (myWorker == null || myWorker.isDisposed()) {
2133 myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
2134 myWorker.start();
2135 if (first) {
2136 myWorker.addTaskFirst(pooledThreadRunnable);
2138 else {
2139 myWorker.addTask(pooledThreadRunnable);
2141 myWorker.dispose(false);
2143 else {
2144 if (first) {
2145 myWorker.addTaskFirst(pooledThreadRunnable);
2147 else {
2148 myWorker.addTask(pooledThreadRunnable);
2153 private void registerWorkerTask(Runnable runnable) {
2154 synchronized (myActiveWorkerTasks) {
2155 myActiveWorkerTasks.add(runnable);
2159 private void unregisterWorkerTask(Runnable runnable, @Nullable Runnable finalizeRunnable) {
2160 boolean wasRemoved;
2161 synchronized (myActiveWorkerTasks) {
2162 wasRemoved = myActiveWorkerTasks.remove(runnable);
2165 if (wasRemoved && finalizeRunnable != null) {
2166 finalizeRunnable.run();
2169 maybeReady();
2172 public boolean isWorkerBusy() {
2173 synchronized (myActiveWorkerTasks) {
2174 return myActiveWorkerTasks.size() > 0;
2178 private void clearWorkerTasks() {
2179 synchronized (myActiveWorkerTasks) {
2180 myActiveWorkerTasks.clear();
2184 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node, boolean updatePosition) {
2185 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2186 NodeDescriptor descriptor = getDescriptorFrom(node);
2187 if (getElementFromDescriptor(descriptor) == null) return;
2189 boolean notified = false;
2190 if (updatePosition) {
2191 DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
2192 if (parentNode != null) {
2193 int oldIndex = parentNode.getIndex(node);
2194 int newIndex = oldIndex;
2195 if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
2196 final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
2197 for (int i = 0; i < parentNode.getChildCount(); i++) {
2198 children.add(parentNode.getChildAt(i));
2200 sortChildren(node, children);
2201 newIndex = children.indexOf(node);
2204 if (oldIndex != newIndex) {
2205 List<Object> pathsToExpand = new ArrayList<Object>();
2206 List<Object> selectionPaths = new ArrayList<Object>();
2207 TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
2208 removeNodeFromParent(node, false);
2209 myTreeModel.insertNodeInto(node, parentNode, newIndex);
2210 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
2211 notified = true;
2213 else {
2214 myTreeModel.nodeChanged(node);
2215 notified = true;
2218 else {
2219 myTreeModel.nodeChanged(node);
2220 notified = true;
2224 if (!notified) {
2225 myTreeModel.nodeChanged(node);
2230 public DefaultTreeModel getTreeModel() {
2231 return myTreeModel;
2234 private void insertNodesInto(final ArrayList<TreeNode> toInsert, DefaultMutableTreeNode parentNode) {
2235 if (toInsert.isEmpty()) return;
2237 sortChildren(parentNode, toInsert);
2239 ArrayList<TreeNode> all = new ArrayList<TreeNode>(toInsert.size() + parentNode.getChildCount());
2240 all.addAll(toInsert);
2241 all.addAll(TreeUtil.childrenToArray(parentNode));
2243 sortChildren(parentNode, all);
2245 int[] newNodeIndices = new int[toInsert.size()];
2246 int eachNewNodeIndex = 0;
2247 for (int i = 0; i < toInsert.size(); i++) {
2248 TreeNode eachNewNode = toInsert.get(i);
2249 while (all.get(eachNewNodeIndex) != eachNewNode) eachNewNodeIndex++;
2250 newNodeIndices[i] = eachNewNodeIndex;
2251 parentNode.insert((MutableTreeNode)eachNewNode, eachNewNodeIndex);
2254 myTreeModel.nodesWereInserted(parentNode, newNodeIndices);
2257 private void sortChildren(DefaultMutableTreeNode node, ArrayList<TreeNode> children) {
2258 getBuilder().sortChildren(myNodeComparator, node, children);
2261 private void disposeNode(DefaultMutableTreeNode node) {
2262 removeFromUpdating(node);
2263 removeFromUnbuilt(node);
2265 if (node.getChildCount() > 0) {
2266 for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
2267 disposeNode(_node);
2270 if (isLoadingNode(node)) return;
2271 NodeDescriptor descriptor = getDescriptorFrom(node);
2272 if (descriptor == null) return;
2273 final Object element = getElementFromDescriptor(descriptor);
2274 removeMapping(element, node, null);
2275 node.setUserObject(null);
2276 node.removeAllChildren();
2279 public void addSubtreeToUpdate(final DefaultMutableTreeNode root) {
2280 addSubtreeToUpdate(root, null);
2283 public void addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
2284 getUpdater().runAfterUpdate(runAfterUpdate);
2285 getUpdater().addSubtreeToUpdate(root);
2288 public boolean wasRootNodeInitialized() {
2289 return myRootNodeWasInitialized;
2292 private boolean isRootNodeBuilt() {
2293 return myRootNodeWasInitialized && isNodeBeingBuilt(myRootNode);
2296 public void select(final Object[] elements, @Nullable final Runnable onDone) {
2297 select(elements, onDone, false);
2300 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
2301 select(elements, onDone, addToSelection, false);
2304 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) {
2305 _select(elements, onDone, addToSelection, true, false, true, deferred, false);
2308 void _select(final Object[] elements,
2309 final Runnable onDone,
2310 final boolean addToSelection,
2311 final boolean checkCurrentSelection,
2312 final boolean checkIfInStructure) {
2314 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, true, false, false);
2317 void _select(final Object[] elements,
2318 final Runnable onDone,
2319 final boolean addToSelection,
2320 final boolean checkCurrentSelection,
2321 final boolean checkIfInStructure,
2322 final boolean scrollToVisible) {
2324 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, false, false);
2327 void _select(final Object[] elements,
2328 final Runnable onDone,
2329 final boolean addToSelection,
2330 final boolean checkCurrentSelection,
2331 final boolean checkIfInStructure,
2332 final boolean scrollToVisible,
2333 final boolean deferred,
2334 final boolean canSmartExpand) {
2336 boolean willAffectSelection = elements.length > 0 || (elements.length == 0 && addToSelection);
2337 if (!willAffectSelection) {
2338 runDone(onDone);
2339 return;
2342 final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections;
2344 if (!deferred && wasRootNodeInitialized() && willAffectSelection) {
2345 myCanProcessDeferredSelections = false;
2348 if (!checkDeferred(deferred, onDone)) return;
2350 if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) {
2351 getTree().clearSelection();
2355 runDone(new Runnable() {
2356 public void run() {
2357 if (!checkDeferred(deferred, onDone)) return;
2359 final Set<Object> currentElements = getSelectedElements();
2361 if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
2362 boolean runSelection = false;
2363 for (Object eachToSelect : elements) {
2364 if (!currentElements.contains(eachToSelect)) {
2365 runSelection = true;
2366 break;
2370 if (!runSelection) {
2371 if (elements.length > 0) {
2372 selectVisible(elements[0], onDone, true, true, scrollToVisible);
2374 return;
2378 Set<Object> toSelect = new HashSet<Object>();
2379 myTree.clearSelection();
2380 toSelect.addAll(Arrays.asList(elements));
2381 if (addToSelection) {
2382 toSelect.addAll(currentElements);
2385 if (checkIfInStructure) {
2386 final Iterator<Object> allToSelect = toSelect.iterator();
2387 while (allToSelect.hasNext()) {
2388 Object each = allToSelect.next();
2389 if (!isInStructure(each)) {
2390 allToSelect.remove();
2395 final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
2397 if (wasRootNodeInitialized()) {
2398 final int[] originalRows = myTree.getSelectionRows();
2399 if (!addToSelection) {
2400 myTree.clearSelection();
2402 addNext(elementsToSelect, 0, new Runnable() {
2403 public void run() {
2404 if (getTree().isSelectionEmpty()) {
2405 restoreSelection(currentElements);
2407 runDone(onDone);
2409 }, originalRows, deferred, scrollToVisible, canSmartExpand);
2411 else {
2412 addToDeferred(elementsToSelect, onDone);
2418 private void restoreSelection(Set<Object> selection) {
2419 for (Object each : selection) {
2420 DefaultMutableTreeNode node = getNodeForElement(each, false);
2421 if (node != null && isValidForSelectionAdjusting(node)) {
2422 addSelectionPath(getPathFor(node), false, null);
2428 private void addToDeferred(final Object[] elementsToSelect, final Runnable onDone) {
2429 myDeferredSelections.clear();
2430 myDeferredSelections.add(new Runnable() {
2431 public void run() {
2432 select(elementsToSelect, onDone, false, true);
2437 private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) {
2438 if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) {
2439 return true;
2441 else {
2442 runDone(onDone);
2443 return false;
2447 @NotNull
2448 final Set<Object> getSelectedElements() {
2449 final TreePath[] paths = myTree.getSelectionPaths();
2451 Set<Object> result = new HashSet<Object>();
2452 if (paths != null) {
2453 for (TreePath eachPath : paths) {
2454 if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2455 final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
2456 final Object eachElement = getElementFor(eachNode);
2457 if (eachElement != null) {
2458 result.add(eachElement);
2463 return result;
2467 private void addNext(final Object[] elements,
2468 final int i,
2469 @Nullable final Runnable onDone,
2470 final int[] originalRows,
2471 final boolean deferred,
2472 final boolean scrollToVisible,
2473 final boolean canSmartExpand) {
2474 if (i >= elements.length) {
2475 if (myTree.isSelectionEmpty()) {
2476 myTree.setSelectionRows(originalRows);
2478 runDone(onDone);
2480 else {
2481 if (!checkDeferred(deferred, onDone)) {
2482 return;
2485 doSelect(elements[i], new Runnable() {
2486 public void run() {
2487 if (!checkDeferred(deferred, onDone)) return;
2489 addNext(elements, i + 1, onDone, originalRows, deferred, scrollToVisible, canSmartExpand);
2491 }, true, deferred, i == 0, scrollToVisible, canSmartExpand);
2495 public void select(final Object element, @Nullable final Runnable onDone) {
2496 select(element, onDone, false);
2499 public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
2500 _select(new Object[]{element}, onDone, addToSelection, true, false);
2503 private void doSelect(final Object element,
2504 final Runnable onDone,
2505 final boolean addToSelection,
2506 final boolean deferred,
2507 final boolean canBeCentered,
2508 final boolean scrollToVisible,
2509 boolean canSmartExpand) {
2510 final Runnable _onDone = new Runnable() {
2511 public void run() {
2512 if (!checkDeferred(deferred, onDone)) return;
2513 selectVisible(element, onDone, addToSelection, canBeCentered, scrollToVisible);
2516 _expand(element, _onDone, true, false, canSmartExpand);
2519 public void scrollSelectionToVisible(@Nullable Runnable onDone, boolean shouldBeCentered) {
2520 int[] rows = myTree.getSelectionRows();
2521 if (rows == null || rows.length == 0) {
2522 runDone(onDone);
2523 return;
2527 Object toSelect = null;
2528 for (int eachRow : rows) {
2529 TreePath path = myTree.getPathForRow(eachRow);
2530 toSelect = getElementFor(path.getLastPathComponent());
2531 if (toSelect != null) break;
2534 if (toSelect != null) {
2535 selectVisible(toSelect, onDone, true, shouldBeCentered, true);
2539 private void selectVisible(Object element, final Runnable onDone, boolean addToSelection, boolean canBeCentered, final boolean scroll) {
2540 final DefaultMutableTreeNode toSelect = getNodeForElement(element, false);
2542 if (toSelect == null) {
2543 runDone(onDone);
2544 return;
2547 if (getRootNode() == toSelect && !myTree.isRootVisible()) {
2548 runDone(onDone);
2549 return;
2552 final int row = myTree.getRowForPath(new TreePath(toSelect.getPath()));
2554 if (myUpdaterState != null) {
2555 myUpdaterState.addSelection(element);
2558 if (Registry.is("ide.tree.autoscrollToVCenter") && canBeCentered) {
2559 runDone(new Runnable() {
2560 public void run() {
2561 TreeUtil.showRowCentered(myTree, row, false, scroll).doWhenDone(new Runnable() {
2562 public void run() {
2563 runDone(onDone);
2569 else {
2570 TreeUtil.showAndSelect(myTree, row - 2, row + 2, row, -1, addToSelection, scroll).doWhenDone(new Runnable() {
2571 public void run() {
2572 runDone(onDone);
2578 public void expand(final Object element, @Nullable final Runnable onDone) {
2579 expand(new Object[] {element}, onDone);
2582 public void expand(final Object[] element, @Nullable final Runnable onDone) {
2583 expand(element, onDone, false);
2587 void expand(final Object element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
2588 _expand(new Object[]{element}, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure, false);
2591 void expand(final Object[] element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
2592 _expand(element, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure, false);
2595 void _expand(final Object[] element,
2596 @NotNull final Runnable onDone,
2597 final boolean parentsOnly,
2598 final boolean checkIfInStructure,
2599 final boolean canSmartExpand) {
2601 runDone(new Runnable() {
2602 public void run() {
2603 if (element.length == 0) {
2604 runDone(onDone);
2605 return;
2608 if (myUpdaterState != null) {
2609 myUpdaterState.clearExpansion();
2613 final ActionCallback done = new ActionCallback(element.length);
2614 done.doWhenDone(new Runnable() {
2615 public void run() {
2616 runDone(onDone);
2620 for (final Object toExpand : element) {
2621 _expand(toExpand, new Runnable() {
2622 public void run() {
2623 done.setDone();
2625 }, parentsOnly, checkIfInStructure, canSmartExpand);
2631 public void collapseChildren(final Object element, @Nullable final Runnable onDone) {
2632 runDone(new Runnable() {
2633 public void run() {
2634 final DefaultMutableTreeNode node = getNodeForElement(element, false);
2635 if (node != null) {
2636 getTree().collapsePath(new TreePath(node.getPath()));
2637 runDone(onDone);
2643 private void runDone(@Nullable Runnable done) {
2644 if (isReleased()) return;
2645 if (done == null) return;
2647 if (isYeildingNow()) {
2648 if (!myYeildingDoneRunnables.contains(done)) {
2649 myYeildingDoneRunnables.add(done);
2652 else {
2653 done.run();
2657 private void _expand(final Object element,
2658 @NotNull final Runnable onDone,
2659 final boolean parentsOnly,
2660 boolean checkIfInStructure,
2661 boolean canSmartExpand) {
2663 if (checkIfInStructure && !isInStructure(element)) {
2664 runDone(onDone);
2665 return;
2668 if (wasRootNodeInitialized()) {
2669 List<Object> kidsToExpand = new ArrayList<Object>();
2670 Object eachElement = element;
2671 DefaultMutableTreeNode firstVisible = null;
2672 while (true) {
2673 if (!isValid(eachElement)) break;
2675 firstVisible = getNodeForElement(eachElement, true);
2676 if (eachElement != element || !parentsOnly) {
2677 assert !kidsToExpand.contains(eachElement) :
2678 "Not a valid tree structure, walking up the structure gives many entries for element=" +
2679 eachElement +
2680 ", root=" +
2681 getTreeStructure().getRootElement();
2682 kidsToExpand.add(eachElement);
2684 if (firstVisible != null) break;
2685 eachElement = getTreeStructure().getParentElement(eachElement);
2686 if (eachElement == null) {
2687 firstVisible = null;
2688 break;
2693 if (firstVisible == null) {
2694 runDone(onDone);
2696 else if (kidsToExpand.size() == 0) {
2697 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)firstVisible.getParent();
2698 if (parentNode != null) {
2699 final TreePath parentPath = new TreePath(parentNode.getPath());
2700 if (!myTree.isExpanded(parentPath)) {
2701 expand(parentPath, canSmartExpand);
2704 runDone(onDone);
2706 else {
2707 processExpand(firstVisible, kidsToExpand, kidsToExpand.size() - 1, onDone, canSmartExpand);
2710 else {
2711 deferExpansion(element, onDone, parentsOnly, canSmartExpand);
2715 private void deferExpansion(final Object element, final Runnable onDone, final boolean parentsOnly, final boolean canSmartExpand) {
2716 myDeferredExpansions.add(new Runnable() {
2717 public void run() {
2718 _expand(element, onDone, parentsOnly, false, canSmartExpand);
2723 private void processExpand(final DefaultMutableTreeNode toExpand,
2724 final List kidsToExpand,
2725 final int expandIndex,
2726 @NotNull final Runnable onDone,
2727 final boolean canSmartExpand) {
2729 final Object element = getElementFor(toExpand);
2730 if (element == null) {
2731 runDone(onDone);
2732 return;
2735 addNodeAction(element, new NodeAction() {
2736 public void onReady(final DefaultMutableTreeNode node) {
2738 if (node.getChildCount() > 0 && !myTree.isExpanded(new TreePath(node.getPath()))) {
2739 if (!isAutoExpand(node)) {
2740 expand(node, canSmartExpand);
2744 if (expandIndex <= 0) {
2745 runDone(onDone);
2746 return;
2749 final DefaultMutableTreeNode nextNode = getNodeForElement(kidsToExpand.get(expandIndex - 1), false);
2750 if (nextNode != null) {
2751 processExpand(nextNode, kidsToExpand, expandIndex - 1, onDone, canSmartExpand);
2753 else {
2754 runDone(onDone);
2757 }, true);
2760 if (myTree.isExpanded(getPathFor(toExpand)) && !myUnbuiltNodes.contains(toExpand)) {
2761 processNodeActionsIfReady(toExpand);
2762 } else {
2763 if (!myUnbuiltNodes.contains(toExpand)) {
2764 getUpdater().addSubtreeToUpdate(toExpand);
2765 } else {
2766 expand(toExpand, canSmartExpand);
2772 private String asString(DefaultMutableTreeNode node) {
2773 if (node == null) return null;
2775 StringBuffer children = new StringBuffer(node.toString());
2776 children.append(" [");
2777 for (int i = 0; i < node.getChildCount(); i++) {
2778 children.append(node.getChildAt(i));
2779 if (i < node.getChildCount() - 1) {
2780 children.append(",");
2783 children.append("]");
2785 return children.toString();
2788 @Nullable
2789 public Object getElementFor(Object node) {
2790 if (!(node instanceof DefaultMutableTreeNode)) return null;
2791 return getElementFor((DefaultMutableTreeNode)node);
2794 @Nullable
2795 Object getElementFor(DefaultMutableTreeNode node) {
2796 if (node != null) {
2797 final Object o = node.getUserObject();
2798 if (o instanceof NodeDescriptor) {
2799 return getElementFromDescriptor(((NodeDescriptor)o));
2803 return null;
2806 public final boolean isNodeBeingBuilt(final TreePath path) {
2807 return isNodeBeingBuilt(path.getLastPathComponent());
2810 public final boolean isNodeBeingBuilt(Object node) {
2811 if (isParentLoading(node) || isLoadingParent(node)) return true;
2813 final boolean childrenAreNoLoadedYet = myUnbuiltNodes.contains(node);
2814 if (childrenAreNoLoadedYet) {
2815 if (node instanceof DefaultMutableTreeNode) {
2816 final TreePath nodePath = new TreePath(((DefaultMutableTreeNode)node).getPath());
2817 if (!myTree.isExpanded(nodePath)) return false;
2820 return true;
2824 return false;
2827 private boolean isLoadingParent(Object node) {
2828 if (!(node instanceof DefaultMutableTreeNode)) return false;
2829 return isLoadedInBackground(getElementFor((DefaultMutableTreeNode)node));
2832 public void setTreeStructure(final AbstractTreeStructure treeStructure) {
2833 myTreeStructure = treeStructure;
2834 clearUpdaterState();
2837 public AbstractTreeUpdater getUpdater() {
2838 return myUpdater;
2841 public void setUpdater(final AbstractTreeUpdater updater) {
2842 myUpdater = updater;
2843 if (updater != null && myUpdateIfInactive) {
2844 updater.showNotify();
2848 public DefaultMutableTreeNode getRootNode() {
2849 return myRootNode;
2852 public void setRootNode(@NotNull final DefaultMutableTreeNode rootNode) {
2853 myRootNode = rootNode;
2856 private void dropUpdaterStateIfExternalChange() {
2857 if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) {
2858 clearUpdaterState();
2862 void clearUpdaterState() {
2863 myUpdaterState = null;
2866 private void createMapping(Object element, DefaultMutableTreeNode node) {
2867 if (!myElementToNodeMap.containsKey(element)) {
2868 myElementToNodeMap.put(element, node);
2870 else {
2871 final Object value = myElementToNodeMap.get(element);
2872 final List<DefaultMutableTreeNode> nodes;
2873 if (value instanceof DefaultMutableTreeNode) {
2874 nodes = new ArrayList<DefaultMutableTreeNode>();
2875 nodes.add((DefaultMutableTreeNode)value);
2876 myElementToNodeMap.put(element, nodes);
2878 else {
2879 nodes = (List<DefaultMutableTreeNode>)value;
2881 nodes.add(node);
2885 private void removeMapping(Object element, DefaultMutableTreeNode node, @Nullable Object elementToPutNodeActionsFor) {
2886 final Object value = myElementToNodeMap.get(element);
2887 if (value != null) {
2888 if (value instanceof DefaultMutableTreeNode) {
2889 if (value.equals(node)) {
2890 myElementToNodeMap.remove(element);
2893 else {
2894 List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
2895 final boolean reallyRemoved = nodes.remove(node);
2896 if (reallyRemoved) {
2897 if (nodes.isEmpty()) {
2898 myElementToNodeMap.remove(element);
2904 remapNodeActions(element, elementToPutNodeActionsFor);
2907 private void remapNodeActions(Object element, Object elementToPutNodeActionsFor) {
2908 _remapNodeActions(element, elementToPutNodeActionsFor, myNodeActions);
2909 _remapNodeActions(element, elementToPutNodeActionsFor, myNodeChildrenActions);
2912 private void _remapNodeActions(Object element, Object elementToPutNodeActionsFor, final Map<Object, List<NodeAction>> nodeActions) {
2913 final List<NodeAction> actions = nodeActions.get(element);
2914 nodeActions.remove(element);
2916 if (elementToPutNodeActionsFor != null && actions != null) {
2917 nodeActions.put(elementToPutNodeActionsFor, actions);
2921 private DefaultMutableTreeNode getFirstNode(Object element) {
2922 return findNode(element, 0);
2925 private DefaultMutableTreeNode findNode(final Object element, int startIndex) {
2926 final Object value = getBuilder().findNodeByElement(element);
2927 if (value == null) {
2928 return null;
2930 if (value instanceof DefaultMutableTreeNode) {
2931 return startIndex == 0 ? (DefaultMutableTreeNode)value : null;
2933 final List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
2934 return startIndex < nodes.size() ? nodes.get(startIndex) : null;
2937 protected Object findNodeByElement(Object element) {
2938 if (myElementToNodeMap.containsKey(element)) {
2939 return myElementToNodeMap.get(element);
2942 try {
2943 TREE_NODE_WRAPPER.setValue(element);
2944 return myElementToNodeMap.get(TREE_NODE_WRAPPER);
2946 finally {
2947 TREE_NODE_WRAPPER.setValue(null);
2951 private DefaultMutableTreeNode findNodeForChildElement(DefaultMutableTreeNode parentNode, Object element) {
2952 final Object value = myElementToNodeMap.get(element);
2953 if (value == null) {
2954 return null;
2957 if (value instanceof DefaultMutableTreeNode) {
2958 final DefaultMutableTreeNode elementNode = (DefaultMutableTreeNode)value;
2959 return parentNode.equals(elementNode.getParent()) ? elementNode : null;
2962 final List<DefaultMutableTreeNode> allNodesForElement = (List<DefaultMutableTreeNode>)value;
2963 for (final DefaultMutableTreeNode elementNode : allNodesForElement) {
2964 if (parentNode.equals(elementNode.getParent())) {
2965 return elementNode;
2969 return null;
2972 public void cancelBackgroundLoading() {
2973 if (myWorker != null) {
2974 myWorker.cancelTasks();
2975 clearWorkerTasks();
2978 clearNodeActions();
2981 private void addNodeAction(Object element, NodeAction action, boolean shouldChildrenBeReady) {
2982 _addNodeAction(element, action, myNodeActions);
2983 if (shouldChildrenBeReady) {
2984 _addNodeAction(element, action, myNodeChildrenActions);
2989 private void _addNodeAction(Object element, NodeAction action, Map<Object, List<NodeAction>> map) {
2990 maybeSetBusyAndScheduleWaiterForReady(true);
2991 List<NodeAction> list = map.get(element);
2992 if (list == null) {
2993 list = new ArrayList<NodeAction>();
2994 map.put(element, list);
2996 list.add(action);
3000 private void cleanUpNow() {
3001 if (isReleased()) return;
3003 final UpdaterTreeState state = new UpdaterTreeState(this);
3005 myTree.collapsePath(new TreePath(myTree.getModel().getRoot()));
3006 myTree.clearSelection();
3007 getRootNode().removeAllChildren();
3009 myRootNodeWasInitialized = false;
3010 clearNodeActions();
3011 myElementToNodeMap.clear();
3012 myDeferredSelections.clear();
3013 myDeferredExpansions.clear();
3014 myLoadingParents.clear();
3015 myUnbuiltNodes.clear();
3016 myUpdateFromRootRequested = true;
3018 if (myWorker != null) {
3019 Disposer.dispose(myWorker);
3020 myWorker = null;
3023 myTree.invalidate();
3025 state.restore(null);
3028 public AbstractTreeUi setClearOnHideDelay(final long clearOnHideDelay) {
3029 myClearOnHideDelay = clearOnHideDelay;
3030 return this;
3033 public void setJantorPollPeriod(final long time) {
3034 myJanitorPollPeriod = time;
3037 public void setCheckStructure(final boolean checkStructure) {
3038 myCheckStructure = checkStructure;
3041 private class MySelectionListener implements TreeSelectionListener {
3042 public void valueChanged(final TreeSelectionEvent e) {
3043 dropUpdaterStateIfExternalChange();
3048 private class MyExpansionListener implements TreeExpansionListener {
3049 public void treeExpanded(TreeExpansionEvent event) {
3050 dropUpdaterStateIfExternalChange();
3052 TreePath path = event.getPath();
3054 if (myRequestedExpand != null && !myRequestedExpand.equals(path)) return;
3056 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
3058 if (!myUnbuiltNodes.contains(node)) {
3059 removeLoading(node, false);
3061 boolean hasUnbuiltChildren = false;
3062 for (int i = 0; i < node.getChildCount(); i++) {
3063 DefaultMutableTreeNode each = (DefaultMutableTreeNode)node.getChildAt(i);
3064 if (myUnbuiltNodes.contains(each)) {
3065 makeLoadingOrLeafIfNoChildren(each);
3066 hasUnbuiltChildren = true;
3070 if (hasUnbuiltChildren) {
3071 getUpdater().addSubtreeToUpdate(node);
3073 } else {
3074 getBuilder().expandNodeChildren(node);
3077 processSmartExpand(node, canSmartExpand(node, true));
3078 processNodeActionsIfReady(node);
3081 public void treeCollapsed(TreeExpansionEvent e) {
3082 dropUpdaterStateIfExternalChange();
3084 final TreePath path = e.getPath();
3085 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
3086 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
3089 TreePath pathToSelect = null;
3090 if (isSelectionInside(node)) {
3091 pathToSelect = new TreePath(node.getPath());
3095 NodeDescriptor descriptor = getDescriptorFrom(node);
3096 if (getBuilder().isDisposeOnCollapsing(descriptor)) {
3097 runDone(new Runnable() {
3098 public void run() {
3099 if (isDisposed(node)) return;
3101 TreePath nodePath = new TreePath(node.getPath());
3102 if (myTree.isExpanded(nodePath)) return;
3104 removeChildren(node);
3105 makeLoadingOrLeafIfNoChildren(node);
3108 if (node.equals(getRootNode())) {
3109 if (myTree.isRootVisible()) {
3110 //todo kirillk to investigate -- should be done by standard selction move
3111 //addSelectionPath(new TreePath(getRootNode().getPath()), true, Condition.FALSE);
3114 else {
3115 myTreeModel.reload(node);
3119 if (pathToSelect != null && myTree.isSelectionEmpty()) {
3120 addSelectionPath(pathToSelect, true, Condition.FALSE);
3124 private void removeChildren(DefaultMutableTreeNode node) {
3125 EnumerationCopy copy = new EnumerationCopy(node.children());
3126 while (copy.hasMoreElements()) {
3127 disposeNode((DefaultMutableTreeNode)copy.nextElement());
3129 node.removeAllChildren();
3130 myTreeModel.nodeStructureChanged(node);
3133 private boolean isSelectionInside(DefaultMutableTreeNode parent) {
3134 TreePath path = new TreePath(myTreeModel.getPathToRoot(parent));
3135 TreePath[] paths = myTree.getSelectionPaths();
3136 if (paths == null) return false;
3137 for (TreePath path1 : paths) {
3138 if (path.isDescendant(path1)) return true;
3140 return false;
3144 public boolean isInStructure(@Nullable Object element) {
3145 Object eachParent = element;
3146 while (eachParent != null) {
3147 if (getTreeStructure().getRootElement().equals(eachParent)) return true;
3148 eachParent = getTreeStructure().getParentElement(eachParent);
3151 return false;
3154 interface NodeAction {
3155 void onReady(DefaultMutableTreeNode node);
3158 public void setCanYield(final boolean canYield) {
3159 myCanYield = canYield;
3162 public Collection<TreeUpdatePass> getYeildingPasses() {
3163 return myYeildingPasses;
3166 public boolean isBuilt(Object element) {
3167 if (!myElementToNodeMap.containsKey(element)) return false;
3168 final Object node = myElementToNodeMap.get(element);
3169 return !myUnbuiltNodes.contains(node);
3172 static class LoadedChildren {
3174 private List myElements;
3175 private Map<Object, NodeDescriptor> myDescriptors = new HashMap<Object, NodeDescriptor>();
3176 private Map<NodeDescriptor, Boolean> myChanges = new HashMap<NodeDescriptor, Boolean>();
3178 LoadedChildren(Object[] elements) {
3179 myElements = Arrays.asList(elements != null ? elements : new Object[0]);
3182 void putDescriptor(Object element, NodeDescriptor descriptor, boolean isChanged) {
3183 assert myElements.contains(element);
3184 myDescriptors.put(element, descriptor);
3185 myChanges.put(descriptor, isChanged);
3188 List getElements() {
3189 return myElements;
3192 NodeDescriptor getDescriptor(Object element) {
3193 return myDescriptors.get(element);
3196 @Override
3197 public String toString() {
3198 return Arrays.asList(myElements) + "->" + myChanges;
3201 public boolean isUpdated(Object element) {
3202 NodeDescriptor desc = getDescriptor(element);
3203 return myChanges.get(desc);
3207 UpdaterTreeState getUpdaterState() {
3208 return myUpdaterState;
3211 private ActionCallback addReadyCallback(Object requestor) {
3212 synchronized (myReadyCallbacks) {
3213 ActionCallback cb = myReadyCallbacks.get(requestor);
3214 if (cb == null) {
3215 cb = new ActionCallback();
3216 myReadyCallbacks.put(requestor, cb);
3219 return cb;
3223 private ActionCallback[] getReadyCallbacks(boolean clear) {
3224 synchronized (myReadyCallbacks) {
3225 ActionCallback[] result = myReadyCallbacks.values().toArray(new ActionCallback[myReadyCallbacks.size()]);
3226 if (clear) {
3227 myReadyCallbacks.clear();
3229 return result;