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