1 package com
.intellij
.ide
.util
.treeView
;
3 import com
.intellij
.ide
.IdeBundle
;
4 import com
.intellij
.openapi
.application
.Application
;
5 import com
.intellij
.openapi
.application
.ApplicationManager
;
6 import com
.intellij
.openapi
.application
.ModalityState
;
7 import com
.intellij
.openapi
.diagnostic
.Logger
;
8 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
9 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
10 import com
.intellij
.openapi
.progress
.ProgressManager
;
11 import com
.intellij
.openapi
.util
.*;
12 import com
.intellij
.openapi
.util
.registry
.Registry
;
13 import com
.intellij
.openapi
.util
.registry
.RegistryValue
;
14 import com
.intellij
.openapi
.project
.IndexNotReadyException
;
15 import com
.intellij
.ui
.LoadingNode
;
16 import com
.intellij
.ui
.treeStructure
.Tree
;
17 import com
.intellij
.util
.Alarm
;
18 import com
.intellij
.util
.ArrayUtil
;
19 import com
.intellij
.util
.ConcurrencyUtil
;
20 import com
.intellij
.util
.Time
;
21 import com
.intellij
.util
.concurrency
.WorkerThread
;
22 import com
.intellij
.util
.containers
.HashSet
;
23 import com
.intellij
.util
.enumeration
.EnumerationCopy
;
24 import com
.intellij
.util
.ui
.tree
.TreeUtil
;
25 import com
.intellij
.util
.ui
.update
.Activatable
;
26 import com
.intellij
.util
.ui
.update
.UiNotifyConnector
;
27 import org
.jetbrains
.annotations
.NotNull
;
28 import org
.jetbrains
.annotations
.Nullable
;
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
.*;
38 import java
.util
.List
;
39 import java
.util
.concurrent
.ScheduledExecutorService
;
40 import java
.util
.concurrent
.TimeUnit
;
42 class AbstractTreeUi
{
43 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
44 protected JTree myTree
;// protected for TestNG
45 @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel
;
46 private AbstractTreeStructure myTreeStructure
;
47 private AbstractTreeUpdater myUpdater
;
48 private Comparator
<NodeDescriptor
> myNodeDescriptorComparator
;
49 private final Comparator
<TreeNode
> myNodeComparator
= new Comparator
<TreeNode
>() {
50 public int compare(TreeNode n1
, TreeNode n2
) {
51 if (isLoadingNode(n1
) || isLoadingNode(n2
)) return 0;
52 NodeDescriptor nodeDescriptor1
= (NodeDescriptor
)((DefaultMutableTreeNode
)n1
).getUserObject();
53 NodeDescriptor nodeDescriptor2
= (NodeDescriptor
)((DefaultMutableTreeNode
)n2
).getUserObject();
54 return myNodeDescriptorComparator
!= null
55 ? myNodeDescriptorComparator
.compare(nodeDescriptor1
, nodeDescriptor2
)
56 : nodeDescriptor1
.getIndex() - nodeDescriptor2
.getIndex();
59 private DefaultMutableTreeNode myRootNode
;
60 private final HashMap
<Object
, Object
> myElementToNodeMap
= new HashMap
<Object
, Object
>();
61 private final HashSet
<DefaultMutableTreeNode
> myUnbuiltNodes
= new HashSet
<DefaultMutableTreeNode
>();
62 private TreeExpansionListener myExpansionListener
;
63 private MySelectionListener mySelectionListener
;
64 private WorkerThread myWorker
= null;
65 private ProgressIndicator myProgress
;
66 private static final int WAIT_CURSOR_DELAY
= 100;
67 private AbstractTreeNode
<Object
> TREE_NODE_WRAPPER
;
68 private boolean myRootNodeWasInitialized
= false;
69 private final Map
<Object
, List
<NodeAction
>> myNodeActions
= new HashMap
<Object
, List
<NodeAction
>>();
70 private boolean myUpdateFromRootRequested
;
71 private boolean myWasEverShown
;
72 private boolean myUpdateIfInactive
;
73 private final List
<Object
> myLoadingParents
= new ArrayList
<Object
>();
74 private long myClearOnHideDelay
= -1;
75 private ScheduledExecutorService ourClearanceService
;
76 private final Map
<AbstractTreeUi
, Long
> ourUi2Countdown
= Collections
.synchronizedMap(new WeakHashMap
<AbstractTreeUi
, Long
>());
77 private final List
<Runnable
> myDeferredSelections
= new ArrayList
<Runnable
>();
78 private final List
<Runnable
> myDeferredExpansions
= new ArrayList
<Runnable
>();
79 private UpdaterTreeState myUpdaterState
;
80 private AbstractTreeBuilder myBuilder
;
82 private final Set
<DefaultMutableTreeNode
> myUpdatingChildren
= new HashSet
<DefaultMutableTreeNode
>();
83 private long myJanitorPollPeriod
= Time
.SECOND
* 10;
84 private boolean myCheckStructure
= false;
87 private boolean myCanYield
= false;
89 private final List
<TreeUpdatePass
> myYeildingPasses
= new ArrayList
<TreeUpdatePass
>();
91 private boolean myYeildingNow
;
93 private List
<DefaultMutableTreeNode
> myPendingNodeActions
= new ArrayList
<DefaultMutableTreeNode
>();
94 private List
<Runnable
> myYeildingDoneRunnables
= new ArrayList
<Runnable
>();
96 private Alarm myBusyAlarm
= new Alarm();
97 private Runnable myWaiterForReady
= new Runnable() {
99 maybeSetBusyAndScheduleWaiterForReady(false);
103 private RegistryValue myYeildingUpdate
= Registry
.get("ide.tree.yeildingUiUpdate");
104 private RegistryValue myShowBusyIndicator
= Registry
.get("ide.tree.showBusyIndicator");
105 private RegistryValue myWaitForReadyTime
= Registry
.get("ide.tree.waitForReadyTimout");
107 private boolean myWasEverIndexNotReady
;
109 protected final void init(AbstractTreeBuilder builder
,
111 DefaultTreeModel treeModel
,
112 AbstractTreeStructure treeStructure
,
113 Comparator
<NodeDescriptor
> comparator
) {
115 init(builder
, tree
, treeModel
, treeStructure
, comparator
, true);
118 protected void init(AbstractTreeBuilder builder
,
120 DefaultTreeModel treeModel
,
121 AbstractTreeStructure treeStructure
,
122 Comparator
<NodeDescriptor
> comparator
,
123 boolean updateIfInactive
) {
126 myTreeModel
= treeModel
;
127 TREE_NODE_WRAPPER
= getBuilder().createSearchingTreeNodeWrapper();
128 myTree
.setModel(myTreeModel
);
129 setRootNode((DefaultMutableTreeNode
)treeModel
.getRoot());
130 setTreeStructure(treeStructure
);
131 myNodeDescriptorComparator
= comparator
;
132 myUpdateIfInactive
= updateIfInactive
;
134 myExpansionListener
= new MyExpansionListener();
135 myTree
.addTreeExpansionListener(myExpansionListener
);
137 mySelectionListener
= new MySelectionListener();
138 myTree
.addTreeSelectionListener(mySelectionListener
);
140 setUpdater(getBuilder().createUpdater());
141 myProgress
= getBuilder().createProgressIndicator();
142 Disposer
.register(getBuilder(), getUpdater());
144 final UiNotifyConnector uiNotify
= new UiNotifyConnector(tree
, new Activatable() {
145 public void showNotify() {
147 AbstractTreeUi
.this.showNotify();
151 public void hideNotify() {
153 AbstractTreeUi
.this.hideNotify();
157 Disposer
.register(getBuilder(), uiNotify
);
160 protected void hideNotify() {
161 myBusyAlarm
.cancelAllRequests();
163 if (!myWasEverShown
) return;
165 if (!myNodeActions
.isEmpty()) {
166 cancelBackgroundLoading();
167 myUpdateFromRootRequested
= true;
170 if (myClearOnHideDelay
>= 0) {
171 ourUi2Countdown
.put(this, System
.currentTimeMillis() + myClearOnHideDelay
);
172 initClearanceServiceIfNeeded();
176 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy
) {
177 if (!myShowBusyIndicator
.asBoolean() || !canYield()) return;
179 if (myTree
instanceof com
.intellij
.ui
.treeStructure
.Tree
) {
180 final com
.intellij
.ui
.treeStructure
.Tree tree
= (Tree
)myTree
;
181 final boolean isBusy
= !isReady() || forcedBusy
;
182 if (isBusy
&& tree
.isShowing()) {
183 tree
.setPaintBusy(true);
184 myBusyAlarm
.cancelAllRequests();
185 myBusyAlarm
.addRequest(myWaiterForReady
, myWaitForReadyTime
.asInteger());
187 tree
.setPaintBusy(false);
192 private void initClearanceServiceIfNeeded() {
193 if (ourClearanceService
!= null) return;
195 ourClearanceService
= ConcurrencyUtil
.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor");
196 ourClearanceService
.scheduleWithFixedDelay(new Runnable() {
200 }, myJanitorPollPeriod
, myJanitorPollPeriod
, TimeUnit
.MILLISECONDS
);
203 private void cleanUpAll() {
204 final long now
= System
.currentTimeMillis();
205 final AbstractTreeUi
[] uis
= ourUi2Countdown
.keySet().toArray(new AbstractTreeUi
[ourUi2Countdown
.size()]);
206 for (AbstractTreeUi eachUi
: uis
) {
207 if (eachUi
== null) continue;
208 final Long timeToCleanup
= ourUi2Countdown
.get(eachUi
);
209 if (timeToCleanup
== null) continue;
210 if (now
>= timeToCleanup
.longValue()) {
211 ourUi2Countdown
.remove(eachUi
);
212 getBuilder().cleanUp();
217 protected void doCleanUp() {
218 final Application app
= ApplicationManager
.getApplication();
219 if (app
!= null && app
.isUnitTestMode()) {
224 //noinspection SSBasedInspection
225 SwingUtilities
.invokeLater(new Runnable() {
235 private void disposeClearanceService() {
236 if (ourClearanceService
!= null) {
237 ourClearanceService
.shutdown();
238 ourClearanceService
= null;
243 ourUi2Countdown
.remove(this);
245 if (!myWasEverShown
|| myUpdateFromRootRequested
) {
246 if (wasRootNodeInitialized()) {
247 getBuilder().updateFromRoot();
250 initRootNodeNowIfNeeded(new TreeUpdatePass(getRootNode()));
251 getBuilder().updateFromRoot();
254 myWasEverShown
= true;
257 public void release() {
258 if (isReleased()) return;
260 myTree
.removeTreeExpansionListener(myExpansionListener
);
261 myTree
.removeTreeSelectionListener(mySelectionListener
);
262 disposeNode(getRootNode());
263 myElementToNodeMap
.clear();
264 getUpdater().cancelAllRequests();
265 if (myWorker
!= null) {
266 myWorker
.dispose(true);
268 TREE_NODE_WRAPPER
.setValue(null);
269 if (myProgress
!= null) {
272 disposeClearanceService();
277 //todo [kirillk] afraid to do so just in release day, to uncomment
278 // myTreeStructure = null;
282 public boolean isReleased() {
283 return myBuilder
== null;
286 protected void doExpandNodeChildren(final DefaultMutableTreeNode node
) {
287 getTreeStructure().commit();
288 addNodeAction(getElementFor(node
), new NodeAction() {
289 public void onReady(final DefaultMutableTreeNode node
) {
290 processSmartExpand(node
);
293 getUpdater().addSubtreeToUpdate(node
);
294 getUpdater().performUpdate();
297 public final AbstractTreeStructure
getTreeStructure() {
298 return myTreeStructure
;
301 public final JTree
getTree() {
306 public final DefaultMutableTreeNode
getNodeForElement(Object element
, final boolean validateAgainstStructure
) {
307 DefaultMutableTreeNode result
= null;
308 if (validateAgainstStructure
) {
311 final DefaultMutableTreeNode node
= findNode(element
, index
);
312 if (node
== null) break;
314 if (isNodeValidForElement(element
, node
)) {
323 result
= getFirstNode(element
);
327 if (result
!= null && !isNodeInStructure(result
)) {
335 private boolean isNodeInStructure(DefaultMutableTreeNode node
) {
336 return TreeUtil
.isAncestor(getRootNode(), node
) && getRootNode() == myTreeModel
.getRoot();
339 private boolean isNodeValidForElement(final Object element
, final DefaultMutableTreeNode node
) {
340 return isSameHierarchy(element
, node
) || isValidChildOfParent(element
, node
);
343 private boolean isValidChildOfParent(final Object element
, final DefaultMutableTreeNode node
) {
344 final DefaultMutableTreeNode parent
= (DefaultMutableTreeNode
)node
.getParent();
345 final Object parentElement
= getElementFor(parent
);
346 if (!isInStructure(parentElement
)) return false;
348 if (parent
instanceof ElementNode
) {
349 return ((ElementNode
)parent
).isValidChild(element
);
352 for (int i
= 0; i
< parent
.getChildCount(); i
++) {
353 final TreeNode child
= parent
.getChildAt(i
);
354 final Object eachElement
= getElementFor(child
);
355 if (element
.equals(eachElement
)) return true;
362 private boolean isSameHierarchy(Object eachParent
, DefaultMutableTreeNode eachParentNode
) {
363 boolean valid
= true;
365 if (eachParent
== null) {
366 valid
= eachParentNode
== null;
370 if (!eachParent
.equals(getElementFor(eachParentNode
))) {
375 eachParent
= getTreeStructure().getParentElement(eachParent
);
376 eachParentNode
= (DefaultMutableTreeNode
)eachParentNode
.getParent();
381 public final DefaultMutableTreeNode
getNodeForPath(Object
[] path
) {
382 DefaultMutableTreeNode node
= null;
383 for (final Object pathElement
: path
) {
384 node
= node
== null ?
getFirstNode(pathElement
) : findNodeForChildElement(node
, pathElement
);
392 public final void buildNodeForElement(Object element
) {
393 getUpdater().performUpdate();
394 DefaultMutableTreeNode node
= getNodeForElement(element
, false);
396 final java
.util
.List
<Object
> elements
= new ArrayList
<Object
>();
398 element
= getTreeStructure().getParentElement(element
);
399 if (element
== null) {
402 elements
.add(0, element
);
405 for (final Object element1
: elements
) {
406 node
= getNodeForElement(element1
, false);
414 public final void buildNodeForPath(Object
[] path
) {
415 getUpdater().performUpdate();
416 DefaultMutableTreeNode node
= null;
417 for (final Object pathElement
: path
) {
418 node
= node
== null ?
getFirstNode(pathElement
) : findNodeForChildElement(node
, pathElement
);
419 if (node
!= null && node
!= path
[path
.length
- 1]) {
425 public final void setNodeDescriptorComparator(Comparator
<NodeDescriptor
> nodeDescriptorComparator
) {
426 myNodeDescriptorComparator
= nodeDescriptorComparator
;
427 List
<Object
> pathsToExpand
= new ArrayList
<Object
>();
428 List
<Object
> selectionPaths
= new ArrayList
<Object
>();
429 TreeBuilderUtil
.storePaths(getBuilder(), getRootNode(), pathsToExpand
, selectionPaths
, false);
430 resortChildren(getRootNode());
431 myTreeModel
.nodeStructureChanged(getRootNode());
432 TreeBuilderUtil
.restorePaths(getBuilder(), pathsToExpand
, selectionPaths
, false);
435 protected AbstractTreeBuilder
getBuilder() {
439 private void resortChildren(DefaultMutableTreeNode node
) {
440 ArrayList
<TreeNode
> childNodes
= TreeUtil
.childrenToArray(node
);
441 node
.removeAllChildren();
442 Collections
.sort(childNodes
, myNodeComparator
);
443 for (TreeNode childNode1
: childNodes
) {
444 DefaultMutableTreeNode childNode
= (DefaultMutableTreeNode
)childNode1
;
446 resortChildren(childNode
);
450 protected final void initRootNode() {
451 final Activatable activatable
= new Activatable() {
452 public void showNotify() {
453 if (!myRootNodeWasInitialized
) {
454 initRootNodeNowIfNeeded(new TreeUpdatePass(getRootNode()));
458 public void hideNotify() {
462 if (myUpdateIfInactive
|| ApplicationManager
.getApplication().isUnitTestMode()) {
463 activatable
.showNotify();
466 new UiNotifyConnector
.Once(myTree
, activatable
);
470 private void initRootNodeNowIfNeeded(TreeUpdatePass pass
) {
471 if (myRootNodeWasInitialized
) return;
473 myRootNodeWasInitialized
= true;
474 Object rootElement
= getTreeStructure().getRootElement();
475 addNodeAction(rootElement
, new NodeAction() {
476 public void onReady(final DefaultMutableTreeNode node
) {
477 processDeferredActions();
480 NodeDescriptor nodeDescriptor
= getTreeStructure().createDescriptor(rootElement
, null);
481 getRootNode().setUserObject(nodeDescriptor
);
482 update(nodeDescriptor
, false);
483 if (getElementFromDescriptor(nodeDescriptor
) != null) {
484 createMapping(getElementFromDescriptor(nodeDescriptor
), getRootNode());
486 addLoadingNode(getRootNode());
487 boolean willUpdate
= false;
488 if (getBuilder().isAutoExpandNode(nodeDescriptor
)) {
489 willUpdate
= myUnbuiltNodes
.contains(getRootNode());
490 expand(getRootNode());
493 updateNodeChildren(getRootNode(), pass
);
495 if (getRootNode().getChildCount() == 0) {
496 myTreeModel
.nodeChanged(getRootNode());
499 if (!myLoadingParents
.contains(getTreeStructure().getRootElement())) {
500 processDeferredActions();
504 private boolean update(final NodeDescriptor nodeDescriptor
, boolean canBeNonEdt
) {
506 assertIsDispatchThread();
509 final Application app
= ApplicationManager
.getApplication();
510 if (app
.isDispatchThread()) {
511 return getBuilder().updateNodeDescriptor(nodeDescriptor
);
513 app
.invokeLater(new Runnable() {
516 getBuilder().updateNodeDescriptor(nodeDescriptor
);
519 }, ModalityState
.stateForComponent(myTree
));
524 private void assertIsDispatchThread() {
525 if (myTree
.isShowing()) {
526 ApplicationManager
.getApplication().assertIsDispatchThread();
530 private void processDeferredActions() {
531 processDeferredActions(myDeferredSelections
);
532 processDeferredActions(myDeferredExpansions
);
535 private void processDeferredActions(List
<Runnable
> actions
) {
536 final Runnable
[] runnables
= actions
.toArray(new Runnable
[actions
.size()]);
538 for (Runnable runnable
: runnables
) {
543 public void doUpdateFromRoot() {
544 updateSubtree(getRootNode());
547 public ActionCallback
doUpdateFromRootCB() {
548 final ActionCallback cb
= new ActionCallback();
549 getUpdater().runAfterUpdate(new Runnable() {
554 updateSubtree(getRootNode());
558 public final void updateSubtree(DefaultMutableTreeNode node
) {
559 updateSubtree(new TreeUpdatePass(node
));
562 public final void updateSubtree(TreeUpdatePass pass
) {
563 maybeSetBusyAndScheduleWaiterForReady(true);
565 initRootNodeNowIfNeeded(pass
);
567 final DefaultMutableTreeNode node
= pass
.getNode();
569 if (!(node
.getUserObject() instanceof NodeDescriptor
)) return;
571 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
573 getBuilder().updateNode(node
);
574 updateNodeChildren(node
, pass
);
578 UpdaterTreeState
setUpdaterState(UpdaterTreeState state
) {
579 final UpdaterTreeState oldState
= myUpdaterState
;
580 if (oldState
== null) {
581 myUpdaterState
= state
;
585 oldState
.addAll(state
);
590 protected void doUpdateNode(DefaultMutableTreeNode node
) {
591 if (!(node
.getUserObject() instanceof NodeDescriptor
)) return;
592 NodeDescriptor descriptor
= (NodeDescriptor
)node
.getUserObject();
593 Object prevElement
= getElementFromDescriptor(descriptor
);
594 if (prevElement
== null) return;
595 boolean changes
= update(descriptor
, false);
596 if (getElementFromDescriptor(descriptor
) == null) {
597 LOG
.assertTrue(false, "element == null, updateSubtree should be invoked for parent! builder=" +
603 "; parentDescriptor=" +
604 descriptor
.getParentDescriptor());
607 updateNodeImageAndPosition(node
);
611 public Object
getElementFromDescriptor(NodeDescriptor descriptor
) {
612 return getBuilder().getTreeStructureElement(descriptor
);
615 private void updateNodeChildren(final DefaultMutableTreeNode node
, final TreeUpdatePass pass
) {
616 getTreeStructure().commit();
617 final boolean wasExpanded
= myTree
.isExpanded(new TreePath(node
.getPath()));
618 final boolean wasLeaf
= node
.getChildCount() == 0;
620 final NodeDescriptor descriptor
= (NodeDescriptor
)node
.getUserObject();
622 if (descriptor
== null) return;
624 if (myUnbuiltNodes
.contains(node
)) {
625 processUnbuilt(node
, descriptor
, pass
);
626 processNodeActionsIfReady(node
);
630 if (getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor
))) {
631 if (queueBackgroundUpdate(node
, descriptor
, pass
)) return;
634 final Map
<Object
, Integer
> elementToIndexMap
= collectElementToIndexMap(descriptor
);
636 myUpdatingChildren
.add(node
);
637 processAllChildren(node
, elementToIndexMap
, pass
).doWhenDone(new Runnable() {
640 removeLoadingNode(node
);
643 ArrayList
<TreeNode
> nodesToInsert
= collectNodesToInsert(descriptor
, elementToIndexMap
);
645 insertNodesInto(nodesToInsert
, node
);
647 updateNodesToInsert(nodesToInsert
, pass
);
653 if (wasExpanded
|| wasLeaf
) {
654 expand(node
, descriptor
, wasLeaf
);
657 myUpdatingChildren
.remove(node
);
659 final Object element
= getElementFor(node
);
660 addNodeAction(element
, new NodeAction() {
661 public void onReady(final DefaultMutableTreeNode node
) {
662 removeLoadingNode(node
);
666 processNodeActionsIfReady(node
);
671 private void expand(DefaultMutableTreeNode node
) {
672 expand(new TreePath(node
.getPath()));
675 private void ____expand(final TreePath path
) {
676 if (path
== null) return;
677 final Object last
= path
.getLastPathComponent();
678 final TreePath parent
= path
.getParentPath();
680 if (myTree
.isExpanded(path
)) {
681 if (last
instanceof DefaultMutableTreeNode
) {
682 processNodeActionsIfReady((DefaultMutableTreeNode
)last
);
686 if (parent
!= null) {
687 final DefaultMutableTreeNode parentNode
= (DefaultMutableTreeNode
)parent
.getLastPathComponent();
688 final TreePath parentPath
= new TreePath(parentNode
.getPath());
689 if (!myTree
.isExpanded(parentPath
)) {
690 myUnbuiltNodes
.add(parentNode
);
691 expandPath(parentPath
);
698 private void expand(final TreePath path
) {
699 if (path
== null) return;
700 final Object last
= path
.getLastPathComponent();
701 boolean isLeaf
= myTree
.getModel().isLeaf(path
.getLastPathComponent());
702 final boolean isRoot
= last
== myTree
.getModel().getRoot();
703 final TreePath parent
= path
.getParentPath();
705 processNodeActionsIfReady((DefaultMutableTreeNode
)last
);
707 else if (myTree
.isExpanded(path
) || (isLeaf
&& parent
!= null && myTree
.isExpanded(parent
))) {
708 if (last
instanceof DefaultMutableTreeNode
) {
709 processNodeActionsIfReady((DefaultMutableTreeNode
)last
);
713 if (isLeaf
&& parent
!= null) {
714 final DefaultMutableTreeNode parentNode
= (DefaultMutableTreeNode
)parent
.getLastPathComponent();
715 if (parentNode
!= null) {
716 myUnbuiltNodes
.add(parentNode
);
726 private void processUnbuilt(final DefaultMutableTreeNode node
, final NodeDescriptor descriptor
, final TreeUpdatePass pass
) {
727 if (getBuilder().isAlwaysShowPlus(descriptor
)) return; // check for isAlwaysShowPlus is important for e.g. changing Show Members state!
729 final Object element
= getBuilder().getTreeStructureElement(descriptor
);
731 if (getTreeStructure().isToBuildChildrenInBackground(element
)) return; //?
733 final Object
[] children
= getChildrenFor(element
);
734 if (children
.length
== 0) {
735 removeLoadingNode(node
);
737 else if (getBuilder().isAutoExpandNode((NodeDescriptor
)node
.getUserObject())) {
738 addNodeAction(getElementFor(node
), new NodeAction() {
739 public void onReady(final DefaultMutableTreeNode node
) {
740 final TreePath path
= new TreePath(node
.getPath());
741 if (getTree().isExpanded(path
) || children
.length
== 0) {
742 removeLoadingNode(node
);
744 maybeYeild(new ActiveRunnable() {
745 public ActionCallback
run() {
746 expand(element
, null);
747 return new ActionCallback
.Done();
756 private void removeLoadingNode(final DefaultMutableTreeNode parent
) {
757 for (int i
= 0; i
< parent
.getChildCount(); i
++) {
758 if (removeIfLoading(parent
.getChildAt(i
))) break;
760 myUnbuiltNodes
.remove(parent
);
763 private boolean removeIfLoading(TreeNode node
) {
764 if (isLoadingNode(node
)) {
765 removeNodeFromParent((MutableTreeNode
)node
, false);
772 //todo [kirillk] temporary consistency check
773 private Object
[] getChildrenFor(final Object element
) {
774 final Object
[] passOne
;
776 passOne
= getTreeStructure().getChildElements(element
);
778 catch (IndexNotReadyException e
) {
779 if (!myWasEverIndexNotReady
) {
780 myWasEverIndexNotReady
= true;
781 LOG
.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
783 return ArrayUtil
.EMPTY_OBJECT_ARRAY
;
786 if (!myCheckStructure
) return passOne
;
788 final Object
[] passTwo
= getTreeStructure().getChildElements(element
);
790 final HashSet two
= new HashSet(Arrays
.asList(passTwo
));
792 if (passOne
.length
!= passTwo
.length
) {
794 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
798 for (Object eachInOne
: passOne
) {
799 if (!two
.contains(eachInOne
)) {
801 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
811 private void updateNodesToInsert(final ArrayList
<TreeNode
> nodesToInsert
, TreeUpdatePass pass
) {
812 for (TreeNode aNodesToInsert
: nodesToInsert
) {
813 DefaultMutableTreeNode childNode
= (DefaultMutableTreeNode
)aNodesToInsert
;
814 addLoadingNode(childNode
);
815 updateNodeChildren(childNode
, pass
);
819 private ActionCallback
processAllChildren(final DefaultMutableTreeNode node
,
820 final Map
<Object
, Integer
> elementToIndexMap
,
821 final TreeUpdatePass pass
) {
823 final ArrayList
<TreeNode
> childNodes
= TreeUtil
.childrenToArray(node
);
825 return maybeYeild(new ActiveRunnable() {
826 public ActionCallback
run() {
827 return processAllChildren(node
, elementToIndexMap
, pass
, childNodes
);
832 private ActionCallback
maybeYeild(final ActiveRunnable processRunnable
, final TreeUpdatePass pass
, final DefaultMutableTreeNode node
) {
833 final ActionCallback result
= new ActionCallback();
835 if (isToYieldUpdateFor(node
)) {
836 pass
.setCurrentNode(node
);
837 yieldAndRun(new Runnable() {
839 if (pass
.isExpired()) return;
841 if (getUpdater().isRerunNeededFor(pass
)) {
842 getUpdater().addSubtreeToUpdate(pass
);
843 result
.setRejected();
846 processRunnable
.run().notify(result
);
852 processRunnable
.run().notify(result
);
858 private void yieldAndRun(final Runnable runnable
, final TreeUpdatePass pass
) {
859 myYeildingPasses
.add(pass
);
860 myYeildingNow
= true;
861 yield(new Runnable() {
867 runOnYeildingDone(new Runnable() {
872 executeYeildingRequest(runnable
, pass
);
879 public boolean isYeildingNow() {
880 return myYeildingNow
;
883 private boolean hasSheduledUpdates() {
884 return getUpdater().hasNodesToUpdate() || myLoadingParents
.size() > 0;
887 private boolean hasExpandedUnbuiltNodes() {
888 for (DefaultMutableTreeNode each
: myUnbuiltNodes
) {
889 if (myTree
.isExpanded(new TreePath(each
.getPath()))) return true;
895 public boolean isReady() {
896 return !isYeildingNow() && !hasSheduledUpdates() && !hasExpandedUnbuiltNodes();
899 private void executeYeildingRequest(Runnable runnable
, TreeUpdatePass pass
) {
901 myYeildingPasses
.remove(pass
);
905 maybeYeildingFinished();
909 private void maybeYeildingFinished() {
910 if (myYeildingPasses
.size() == 0) {
911 myYeildingNow
= false;
912 flushPendingNodeActions();
916 private void flushPendingNodeActions() {
917 final DefaultMutableTreeNode
[] nodes
= myPendingNodeActions
.toArray(new DefaultMutableTreeNode
[myPendingNodeActions
.size()]);
918 myPendingNodeActions
.clear();
920 for (DefaultMutableTreeNode each
: nodes
) {
921 processNodeActionsIfReady(each
);
924 final Runnable
[] actions
= myYeildingDoneRunnables
.toArray(new Runnable
[myYeildingDoneRunnables
.size()]);
925 for (Runnable each
: actions
) {
926 if (!isYeildingNow()) {
927 myYeildingDoneRunnables
.remove(each
);
933 protected void runOnYeildingDone(Runnable onDone
) {
934 getBuilder().runOnYeildingDone(onDone
);
937 protected void yield(Runnable runnable
) {
938 getBuilder().yield(runnable
);
941 private ActionCallback
processAllChildren(final DefaultMutableTreeNode node
,
942 final Map
<Object
, Integer
> elementToIndexMap
,
943 final TreeUpdatePass pass
,
944 final ArrayList
<TreeNode
> childNodes
) {
947 if (pass
.isExpired()) return new ActionCallback
.Rejected();
949 if (childNodes
.size() == 0) return new ActionCallback
.Done();
952 final ActionCallback result
= new ActionCallback(childNodes
.size());
954 for (TreeNode childNode1
: childNodes
) {
955 final DefaultMutableTreeNode childNode
= (DefaultMutableTreeNode
)childNode1
;
956 if (isLoadingNode(childNode
)) {
961 maybeYeild(new ActiveRunnable() {
963 public ActionCallback
run() {
964 return processChildNode(childNode
, (NodeDescriptor
)childNode
.getUserObject(), node
, elementToIndexMap
, pass
);
966 }, pass
, node
).notify(result
);
972 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node
) {
973 if (!canYield()) return false;
974 return getBuilder().isToYieldUpdateFor(node
);
977 private Map
<Object
, Integer
> collectElementToIndexMap(final NodeDescriptor descriptor
) {
978 Map
<Object
, Integer
> elementToIndexMap
= new LinkedHashMap
<Object
, Integer
>();
979 Object
[] children
= getChildrenFor(getBuilder().getTreeStructureElement(descriptor
));
981 for (Object child
: children
) {
982 if (!isValid(child
)) continue;
983 elementToIndexMap
.put(child
, Integer
.valueOf(index
));
986 return elementToIndexMap
;
989 private void expand(final DefaultMutableTreeNode node
, final NodeDescriptor descriptor
, final boolean wasLeaf
) {
990 final Alarm alarm
= new Alarm(Alarm
.ThreadToUse
.SHARED_THREAD
);
991 alarm
.addRequest(new Runnable() {
993 myTree
.setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
995 }, WAIT_CURSOR_DELAY
);
997 if (wasLeaf
&& getBuilder().isAutoExpandNode(descriptor
)) {
1001 ArrayList
<TreeNode
> nodes
= TreeUtil
.childrenToArray(node
);
1002 for (TreeNode node1
: nodes
) {
1003 final DefaultMutableTreeNode childNode
= (DefaultMutableTreeNode
)node1
;
1004 if (isLoadingNode(childNode
)) continue;
1005 NodeDescriptor childDescr
= (NodeDescriptor
)childNode
.getUserObject();
1006 if (getBuilder().isAutoExpandNode(childDescr
)) {
1007 addNodeAction(getElementFor(childNode
), new NodeAction() {
1008 public void onReady(DefaultMutableTreeNode node
) {
1012 addSubtreeToUpdate(childNode
);
1016 int n
= alarm
.cancelAllRequests();
1018 myTree
.setCursor(Cursor
.getDefaultCursor());
1022 public static boolean isLoadingNode(final Object node
) {
1023 return node
instanceof LoadingNode
;
1026 private ArrayList
<TreeNode
> collectNodesToInsert(final NodeDescriptor descriptor
, final Map
<Object
, Integer
> elementToIndexMap
) {
1027 ArrayList
<TreeNode
> nodesToInsert
= new ArrayList
<TreeNode
>();
1028 for (Map
.Entry
<Object
, Integer
> entry
: elementToIndexMap
.entrySet()) {
1029 Object child
= entry
.getKey();
1030 Integer index
= entry
.getValue();
1031 final NodeDescriptor childDescr
= getTreeStructure().createDescriptor(child
, descriptor
);
1032 //noinspection ConstantConditions
1033 if (childDescr
== null) {
1034 LOG
.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child
);
1037 childDescr
.setIndex(index
.intValue());
1038 update(childDescr
, false);
1039 if (getElementFromDescriptor(childDescr
) == null) {
1040 LOG
.error("childDescr.getElement() == null, child = " + child
+ ", builder = " + this);
1043 final DefaultMutableTreeNode childNode
= createChildNode(childDescr
);
1044 nodesToInsert
.add(childNode
);
1045 createMapping(getElementFromDescriptor(childDescr
), childNode
);
1047 return nodesToInsert
;
1050 protected DefaultMutableTreeNode
createChildNode(final NodeDescriptor descriptor
) {
1051 return new ElementNode(this, descriptor
);
1054 protected boolean canYield() {
1055 return myCanYield
&& myYeildingUpdate
.asBoolean();
1058 static class ElementNode
extends DefaultMutableTreeNode
{
1060 Set
<Object
> myElements
= new HashSet
<Object
>();
1061 AbstractTreeUi myUi
;
1064 ElementNode(AbstractTreeUi ui
, NodeDescriptor descriptor
) {
1070 public void insert(final MutableTreeNode newChild
, final int childIndex
) {
1071 super.insert(newChild
, childIndex
);
1072 final Object element
= myUi
.getElementFor(newChild
);
1073 if (element
!= null) {
1074 myElements
.add(element
);
1079 public void remove(final int childIndex
) {
1080 final TreeNode node
= getChildAt(childIndex
);
1081 super.remove(childIndex
);
1082 final Object element
= myUi
.getElementFor(node
);
1083 if (element
!= null) {
1084 myElements
.remove(element
);
1088 boolean isValidChild(Object childElement
) {
1089 return myElements
.contains(childElement
);
1093 private boolean isUpdatingParent(DefaultMutableTreeNode kid
) {
1094 DefaultMutableTreeNode eachParent
= kid
;
1095 while (eachParent
!= null) {
1096 if (myUpdatingChildren
.contains(eachParent
)) return true;
1097 eachParent
= (DefaultMutableTreeNode
)eachParent
.getParent();
1103 private boolean queueBackgroundUpdate(final DefaultMutableTreeNode node
, final NodeDescriptor descriptor
, final TreeUpdatePass pass
) {
1104 if (myLoadingParents
.contains(getElementFromDescriptor(descriptor
))) return false;
1106 myLoadingParents
.add(getElementFromDescriptor(descriptor
));
1108 if (!isNodeBeingBuilt(node
)) {
1109 LoadingNode loadingNode
= new LoadingNode(getLoadingNodeText());
1110 myTreeModel
.insertNodeInto(loadingNode
, node
, node
.getChildCount());
1113 Runnable updateRunnable
= new Runnable() {
1115 if (isReleased()) return;
1117 update(descriptor
, true);
1118 Object element
= getElementFromDescriptor(descriptor
);
1119 if (element
== null) return;
1121 getChildrenFor(getBuilder().getTreeStructureElement(descriptor
)); // load children
1125 Runnable postRunnable
= new Runnable() {
1127 if (isReleased()) return;
1129 updateNodeChildren(node
, pass
);
1131 myLoadingParents
.remove(getElementFromDescriptor(descriptor
));
1133 update(descriptor
, false);
1134 Object element
= getElementFromDescriptor(descriptor
);
1136 if (element
!= null) {
1137 myUnbuiltNodes
.remove(node
);
1139 for (int i
= 0; i
< node
.getChildCount(); i
++) {
1140 TreeNode child
= node
.getChildAt(i
);
1141 if (isLoadingNode(child
)) {
1142 if (TreeBuilderUtil
.isNodeSelected(myTree
, node
)) {
1143 addSelectionPath(new TreePath(myTreeModel
.getPathToRoot(node
)), true, Condition
.FALSE
);
1145 removeIfLoading(child
);
1150 processNodeActionsIfReady(node
);
1154 addTaskToWorker(updateRunnable
, true, postRunnable
);
1158 private void processNodeActionsIfReady(final DefaultMutableTreeNode node
) {
1159 if (isNodeBeingBuilt(node
)) return;
1161 final Object o
= node
.getUserObject();
1162 if (!(o
instanceof NodeDescriptor
)) return;
1165 if (isYeildingNow()) {
1166 myPendingNodeActions
.add(node
);
1170 final Object element
= getBuilder().getTreeStructureElement((NodeDescriptor
)o
);
1172 final List
<NodeAction
> actions
= myNodeActions
.get(element
);
1173 if (actions
!= null) {
1174 myNodeActions
.remove(element
);
1175 for (NodeAction each
: actions
) {
1180 if (!isUpdatingParent(node
)) {
1181 //if (myUpdaterState != null) {
1182 // if (myUpdaterState.process(node, myTree)) {
1183 // clearUpdaterState();
1187 final UpdaterTreeState state
= myUpdaterState
;
1188 if (myNodeActions
.size() == 0 && state
!= null && !state
.isProcessingNow()) {
1189 if (!state
.restore()) {
1190 setUpdaterState(state
);
1196 private void processSmartExpand(final DefaultMutableTreeNode node
) {
1197 if (getBuilder().isSmartExpand() && node
.getChildCount() == 1) { // "smart" expand
1198 TreeNode childNode
= node
.getChildAt(0);
1199 if (isLoadingNode(childNode
)) return;
1200 final TreePath childPath
= new TreePath(node
.getPath()).pathByAddingChild(childNode
);
1205 public boolean isLoadingChildrenFor(final Object nodeObject
) {
1206 if (!(nodeObject
instanceof DefaultMutableTreeNode
)) return false;
1208 DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)nodeObject
;
1210 int loadingNodes
= 0;
1211 for (int i
= 0; i
< Math
.min(node
.getChildCount(), 2); i
++) {
1212 TreeNode child
= node
.getChildAt(i
);
1213 if (isLoadingNode(child
)) {
1217 return loadingNodes
> 0 && loadingNodes
== node
.getChildCount();
1220 private boolean isParentLoading(Object nodeObject
) {
1221 if (!(nodeObject
instanceof DefaultMutableTreeNode
)) return false;
1223 DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)nodeObject
;
1225 TreeNode eachParent
= node
.getParent();
1227 while (eachParent
!= null) {
1228 eachParent
= eachParent
.getParent();
1229 if (eachParent
instanceof DefaultMutableTreeNode
) {
1230 final Object eachElement
= getElementFor((DefaultMutableTreeNode
)eachParent
);
1231 if (myLoadingParents
.contains(eachElement
)) return true;
1238 protected String
getLoadingNodeText() {
1239 return IdeBundle
.message("progress.searching");
1242 private ActionCallback
processChildNode(final DefaultMutableTreeNode childNode
,
1243 final NodeDescriptor childDescr
,
1244 final DefaultMutableTreeNode node
,
1245 final Map
<Object
, Integer
> elementToIndexMap
,
1246 TreeUpdatePass pass
) {
1248 if (pass
.isExpired()) {
1249 return new ActionCallback
.Rejected();
1253 if (childDescr
== null) {
1255 return new ActionCallback
.Rejected();
1257 Object oldElement
= getElementFromDescriptor(childDescr
);
1258 if (oldElement
== null) {
1260 return new ActionCallback
.Rejected();
1262 boolean changes
= update(childDescr
, false);
1263 Object newElement
= getElementFromDescriptor(childDescr
);
1264 Integer index
= newElement
!= null ? elementToIndexMap
.get(getBuilder().getTreeStructureElement(childDescr
)) : null;
1265 if (index
!= null) {
1266 if (childDescr
.getIndex() != index
.intValue()) {
1269 childDescr
.setIndex(index
.intValue());
1271 if (index
!= null && changes
) {
1272 updateNodeImageAndPosition(childNode
);
1274 if (!oldElement
.equals(newElement
)) {
1275 removeMapping(oldElement
, childNode
);
1276 if (newElement
!= null) {
1277 createMapping(newElement
, childNode
);
1281 if (index
== null) {
1282 int selectedIndex
= -1;
1283 if (TreeBuilderUtil
.isNodeOrChildSelected(myTree
, childNode
)) {
1284 selectedIndex
= node
.getIndex(childNode
);
1287 if (childNode
.getParent() instanceof DefaultMutableTreeNode
) {
1288 final DefaultMutableTreeNode parent
= (DefaultMutableTreeNode
)childNode
.getParent();
1289 if (myTree
.isExpanded(new TreePath(parent
.getPath()))) {
1290 if (parent
.getChildCount() == 1 && parent
.getChildAt(0) == childNode
) {
1291 insertLoadingNode(parent
, false);
1296 Object disposedElement
= getElementFor(childNode
);
1298 removeNodeFromParent(childNode
, selectedIndex
>= 0);
1299 disposeNode(childNode
);
1301 if (selectedIndex
>= 0) {
1302 if (node
.getChildCount() > 0) {
1303 if (node
.getChildCount() > selectedIndex
) {
1304 TreeNode newChildNode
= node
.getChildAt(selectedIndex
);
1305 if (isValidForSelectionAdjusting(newChildNode
)) {
1306 addSelectionPath(new TreePath(myTreeModel
.getPathToRoot(newChildNode
)), true, getExpiredElementCondition(disposedElement
));
1310 TreeNode newChild
= node
.getChildAt(node
.getChildCount() - 1);
1311 if (isValidForSelectionAdjusting(newChild
)) {
1312 addSelectionPath(new TreePath(myTreeModel
.getPathToRoot(newChild
)), true, getExpiredElementCondition(disposedElement
));
1317 addSelectionPath(new TreePath(myTreeModel
.getPathToRoot(node
)), true, getExpiredElementCondition(disposedElement
));
1322 elementToIndexMap
.remove(getBuilder().getTreeStructureElement(childDescr
));
1323 updateNodeChildren(childNode
, pass
);
1326 if (node
.equals(getRootNode())) {
1327 myTreeModel
.nodeChanged(getRootNode());
1330 return new ActionCallback
.Done();
1333 private boolean isValidForSelectionAdjusting(TreeNode node
) {
1334 if (isLoadingNode(node
)) return true;
1336 final Object elementInTree
= getElementFor(node
);
1337 if (elementInTree
== null) return false;
1339 final TreeNode parentNode
= node
.getParent();
1340 final Object parentElementInTree
= getElementFor(parentNode
);
1341 if (parentElementInTree
== null) return false;
1343 final Object parentElement
= getTreeStructure().getParentElement(elementInTree
);
1345 return parentElementInTree
.equals(parentElement
);
1348 private Condition
getExpiredElementCondition(final Object element
) {
1349 return new Condition() {
1350 public boolean value(final Object o
) {
1351 return isInStructure(element
);
1356 private void addSelectionPath(final TreePath path
, final boolean isAdjustedSelection
, final Condition isExpiredAdjustement
) {
1357 doWithUpdaterState(new Runnable() {
1359 TreePath toSelect
= null;
1361 if (isLoadingNode(path
.getLastPathComponent())) {
1362 final TreePath parentPath
= path
.getParentPath();
1363 if (parentPath
!= null) {
1364 toSelect
= parentPath
;
1371 if (toSelect
!= null) {
1372 myTree
.addSelectionPath(toSelect
);
1374 if (isAdjustedSelection
&& myUpdaterState
!= null) {
1375 final Object toSelectElement
= getElementFor(toSelect
.getLastPathComponent());
1376 myUpdaterState
.addAdjustedSelection(toSelectElement
, isExpiredAdjustement
);
1383 private static TreePath
getPathFor(TreeNode node
) {
1384 if (node
instanceof DefaultMutableTreeNode
) {
1385 return new TreePath(((DefaultMutableTreeNode
)node
).getPath());
1388 ArrayList nodes
= new ArrayList();
1389 TreeNode eachParent
= node
;
1390 while (eachParent
!= null) {
1391 nodes
.add(eachParent
);
1392 eachParent
= eachParent
.getParent();
1395 return new TreePath(ArrayUtil
.toObjectArray(nodes
));
1400 private void removeNodeFromParent(final MutableTreeNode node
, final boolean willAdjustSelection
) {
1401 doWithUpdaterState(new Runnable() {
1403 if (willAdjustSelection
) {
1404 final TreePath path
= getPathFor(node
);
1405 if (myTree
.isPathSelected(path
)) {
1406 myTree
.removeSelectionPath(path
);
1410 myTreeModel
.removeNodeFromParent(node
);
1415 private void expandPath(final TreePath path
) {
1416 doWithUpdaterState(new Runnable() {
1418 myTree
.expandPath(path
);
1423 private void doWithUpdaterState(Runnable runnable
) {
1424 if (myUpdaterState
!= null) {
1425 myUpdaterState
.process(runnable
);
1432 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor
) {
1433 return descriptor
.update();
1436 private void addLoadingNode(final DefaultMutableTreeNode node
) {
1437 final NodeDescriptor descriptor
= (NodeDescriptor
)node
.getUserObject();
1438 if (!getBuilder().isAlwaysShowPlus(descriptor
)) {
1439 if (getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor
))) {
1440 final boolean[] hasNoChildren
= new boolean[1];
1441 Runnable runnable
= new Runnable() {
1443 if (isReleased()) return;
1445 update(descriptor
, true);
1446 Object element
= getBuilder().getTreeStructureElement(descriptor
);
1447 if (element
== null && !isValid(element
)) return;
1449 Object
[] children
= getChildrenFor(element
);
1450 hasNoChildren
[0] = children
.length
== 0;
1454 Runnable postRunnable
= new Runnable() {
1456 if (isReleased()) return;
1458 if (hasNoChildren
[0]) {
1459 update(descriptor
, false);
1460 // todo please check the fix
1461 // removing loading node from the same node we've added it to, no need to look for it
1462 removeLoadingNode(node
);
1463 //Object element = getElementFromDescriptor(descriptor);
1464 //if (element != null) {
1465 // DefaultMutableTreeNode node = getNodeForElement(element, false);
1466 // removeLoadingNode(node);
1472 addTaskToWorker(runnable
, false, postRunnable
);
1475 Object
[] children
= getChildrenFor(getBuilder().getTreeStructureElement(descriptor
));
1476 if (children
.length
== 0) return;
1480 insertLoadingNode(node
, true);
1483 private boolean isValid(Object element
) {
1484 if (element
instanceof ValidateableNode
) {
1485 if (!((ValidateableNode
)element
).isValid()) return false;
1487 return getBuilder().validateNode(element
);
1490 private void insertLoadingNode(final DefaultMutableTreeNode node
, boolean addToUnbuilt
) {
1491 myTreeModel
.insertNodeInto(new LoadingNode(), node
, 0);
1493 myUnbuiltNodes
.add(node
);
1497 protected void addTaskToWorker(final Runnable runnable
, boolean first
, final Runnable postRunnable
) {
1498 Runnable runnable1
= new Runnable() {
1500 if (isReleased()) return;
1503 Runnable runnable2
= new Runnable() {
1505 if (isReleased()) return;
1507 final Application app
= ApplicationManager
.getApplication();
1508 if (app
== null) return;
1510 app
.runReadAction(runnable
);
1511 if (postRunnable
!= null) {
1512 final JTree tree
= myTree
;
1513 if (tree
!= null && tree
.isVisible()) {
1514 app
.invokeLater(postRunnable
, ModalityState
.stateForComponent(tree
));
1517 app
.invokeLater(postRunnable
);
1522 if (myProgress
!= null) {
1523 ProgressManager
.getInstance().runProcess(runnable2
, myProgress
);
1529 catch (ProcessCanceledException e
) {
1535 if (myWorker
== null || myWorker
.isDisposed()) {
1536 myWorker
= new WorkerThread("AbstractTreeBuilder.Worker", 1);
1539 myWorker
.addTaskFirst(runnable1
);
1542 myWorker
.addTask(runnable1
);
1544 myWorker
.dispose(false);
1548 myWorker
.addTaskFirst(runnable1
);
1551 myWorker
.addTask(runnable1
);
1556 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node
) {
1557 if (!(node
.getUserObject() instanceof NodeDescriptor
)) return;
1558 NodeDescriptor descriptor
= (NodeDescriptor
)node
.getUserObject();
1559 if (getElementFromDescriptor(descriptor
) == null) return;
1560 DefaultMutableTreeNode parentNode
= (DefaultMutableTreeNode
)node
.getParent();
1561 if (parentNode
!= null) {
1562 int oldIndex
= parentNode
.getIndex(node
);
1563 int newIndex
= oldIndex
;
1564 if (isLoadingChildrenFor(node
.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor
)) {
1565 final ArrayList
<TreeNode
> children
= new ArrayList
<TreeNode
>(parentNode
.getChildCount());
1566 for (int i
= 0; i
< parentNode
.getChildCount(); i
++) {
1567 children
.add(parentNode
.getChildAt(i
));
1569 Collections
.sort(children
, myNodeComparator
);
1570 newIndex
= children
.indexOf(node
);
1573 if (oldIndex
!= newIndex
) {
1574 List
<Object
> pathsToExpand
= new ArrayList
<Object
>();
1575 List
<Object
> selectionPaths
= new ArrayList
<Object
>();
1576 TreeBuilderUtil
.storePaths(getBuilder(), node
, pathsToExpand
, selectionPaths
, false);
1577 removeNodeFromParent(node
, false);
1578 myTreeModel
.insertNodeInto(node
, parentNode
, newIndex
);
1579 TreeBuilderUtil
.restorePaths(getBuilder(), pathsToExpand
, selectionPaths
, false);
1582 myTreeModel
.nodeChanged(node
);
1586 myTreeModel
.nodeChanged(node
);
1590 public DefaultTreeModel
getTreeModel() {
1594 private void insertNodesInto(ArrayList
<TreeNode
> nodes
, DefaultMutableTreeNode parentNode
) {
1595 if (nodes
.isEmpty()) return;
1597 nodes
= new ArrayList
<TreeNode
>(nodes
);
1598 Collections
.sort(nodes
, myNodeComparator
);
1600 ArrayList
<TreeNode
> all
= TreeUtil
.childrenToArray(parentNode
);
1602 Collections
.sort(all
, myNodeComparator
);
1604 int[] indices
= new int[nodes
.size()];
1606 for (int i
= 0; i
< nodes
.size(); i
++) {
1607 TreeNode node
= nodes
.get(i
);
1608 while (all
.get(idx
) != node
) idx
++;
1610 parentNode
.insert((MutableTreeNode
)node
, idx
);
1613 myTreeModel
.nodesWereInserted(parentNode
, indices
);
1616 private void disposeNode(DefaultMutableTreeNode node
) {
1617 if (node
.getChildCount() > 0) {
1618 for (DefaultMutableTreeNode _node
= (DefaultMutableTreeNode
)node
.getFirstChild(); _node
!= null; _node
= _node
.getNextSibling()) {
1622 if (isLoadingNode(node
)) return;
1623 NodeDescriptor descriptor
= (NodeDescriptor
)node
.getUserObject();
1624 if (descriptor
== null) return;
1625 final Object element
= getElementFromDescriptor(descriptor
);
1626 removeMapping(element
, node
);
1627 node
.setUserObject(null);
1628 node
.removeAllChildren();
1631 public void addSubtreeToUpdate(final DefaultMutableTreeNode root
) {
1632 addSubtreeToUpdate(root
, null);
1635 public void addSubtreeToUpdate(final DefaultMutableTreeNode root
, Runnable runAfterUpdate
) {
1636 getUpdater().runAfterUpdate(runAfterUpdate
);
1637 getUpdater().addSubtreeToUpdate(root
);
1640 public boolean wasRootNodeInitialized() {
1641 return myRootNodeWasInitialized
;
1644 public void select(final Object
[] elements
, @Nullable final Runnable onDone
) {
1645 select(elements
, onDone
, false);
1648 public void select(final Object
[] elements
, @Nullable final Runnable onDone
, boolean addToSelection
) {
1649 _select(elements
, onDone
, addToSelection
, true, false);
1652 void _select(final Object
[] elements
,
1653 final Runnable onDone
,
1654 final boolean addToSelection
,
1655 final boolean checkCurrentSelection
,
1656 final boolean checkIfInStructure
) {
1659 runDone(new Runnable() {
1661 if (elements
.length
== 0) {
1666 final Set
<Object
> currentElements
= getSelectedElements();
1668 if (checkCurrentSelection
&& currentElements
.size() > 0 && elements
.length
== currentElements
.size()) {
1669 boolean runSelection
= false;
1670 for (Object eachToSelect
: elements
) {
1671 if (!currentElements
.contains(eachToSelect
)) {
1672 runSelection
= true;
1677 if (!runSelection
) {
1683 Set
<Object
> toSelect
= new HashSet
<Object
>();
1684 myTree
.clearSelection();
1685 toSelect
.addAll(Arrays
.asList(elements
));
1686 if (addToSelection
) {
1687 toSelect
.addAll(currentElements
);
1690 if (checkIfInStructure
) {
1691 final Iterator
<Object
> allToSelect
= toSelect
.iterator();
1692 while (allToSelect
.hasNext()) {
1693 Object each
= allToSelect
.next();
1694 if (!isInStructure(each
)) {
1695 allToSelect
.remove();
1700 final Object
[] elementsToSelect
= ArrayUtil
.toObjectArray(toSelect
);
1702 if (wasRootNodeInitialized()) {
1703 final int[] originalRows
= myTree
.getSelectionRows();
1704 if (!addToSelection
) {
1705 myTree
.clearSelection();
1707 addNext(elementsToSelect
, 0, onDone
, originalRows
);
1710 myDeferredSelections
.clear();
1711 myDeferredSelections
.add(new Runnable() {
1713 select(elementsToSelect
, onDone
);
1722 final Set
<Object
> getSelectedElements() {
1723 final TreePath
[] paths
= myTree
.getSelectionPaths();
1725 Set
<Object
> result
= new HashSet
<Object
>();
1726 if (paths
!= null) {
1727 for (TreePath eachPath
: paths
) {
1728 if (eachPath
.getLastPathComponent() instanceof DefaultMutableTreeNode
) {
1729 final DefaultMutableTreeNode eachNode
= (DefaultMutableTreeNode
)eachPath
.getLastPathComponent();
1730 final Object eachElement
= getElementFor(eachNode
);
1731 if (eachElement
!= null) {
1732 result
.add(eachElement
);
1741 private void addNext(final Object
[] elements
, final int i
, @Nullable final Runnable onDone
, final int[] originalRows
) {
1742 if (i
>= elements
.length
) {
1743 if (myTree
.isSelectionEmpty()) {
1744 myTree
.setSelectionRows(originalRows
);
1749 _select(elements
[i
], new Runnable() {
1751 addNext(elements
, i
+ 1, onDone
, originalRows
);
1757 public void select(final Object element
, @Nullable final Runnable onDone
) {
1758 select(element
, onDone
, false);
1761 public void select(final Object element
, @Nullable final Runnable onDone
, boolean addToSelection
) {
1762 _select(element
, onDone
, addToSelection
);
1765 private void _select(final Object element
, final Runnable onDone
, final boolean addToSelection
) {
1766 final Runnable _onDone
= new Runnable() {
1768 final DefaultMutableTreeNode toSelect
= getNodeForElement(element
, false);
1769 if (toSelect
== null) {
1773 final int row
= myTree
.getRowForPath(new TreePath(toSelect
.getPath()));
1775 if (myUpdaterState
!= null) {
1776 myUpdaterState
.addSelection(element
);
1778 TreeUtil
.showAndSelect(myTree
, row
- 2, row
+ 2, row
, -1, addToSelection
);
1782 _expand(element
, _onDone
, true, false);
1785 public void expand(final Object element
, @Nullable final Runnable onDone
) {
1786 expand(element
, onDone
, false);
1790 void expand(final Object element
, @Nullable final Runnable onDone
, boolean checkIfInStructure
) {
1791 _expand(new Object
[]{element
}, onDone
== null ?
new EmptyRunnable() : onDone
, false, checkIfInStructure
);
1794 void expand(final Object
[] element
, @Nullable final Runnable onDone
, boolean checkIfInStructure
) {
1795 _expand(element
, onDone
== null ?
new EmptyRunnable() : onDone
, false, checkIfInStructure
);
1798 void _expand(final Object
[] element
, @NotNull final Runnable onDone
, final boolean parentsOnly
, final boolean checkIfInStructure
) {
1799 runDone(new Runnable() {
1801 if (element
.length
== 0) {
1806 if (myUpdaterState
!= null) {
1807 myUpdaterState
.clearExpansion();
1811 final ActionCallback done
= new ActionCallback(element
.length
);
1812 done
.doWhenDone(new Runnable() {
1818 for (final Object toExpand
: element
) {
1819 _expand(toExpand
, new Runnable() {
1823 }, parentsOnly
, checkIfInStructure
);
1829 public void collapseChildren(final Object element
, @Nullable final Runnable onDone
) {
1830 runDone(new Runnable() {
1832 final DefaultMutableTreeNode node
= getNodeForElement(element
, false);
1834 getTree().collapsePath(new TreePath(node
.getPath()));
1841 private void runDone(@Nullable Runnable done
) {
1842 if (done
== null) return;
1844 if (isYeildingNow()) {
1845 if (!myYeildingDoneRunnables
.contains(done
)) {
1846 myYeildingDoneRunnables
.add(done
);
1853 private void _expand(final Object element
, @NotNull final Runnable onDone
, final boolean parentsOnly
, boolean checkIfInStructure
) {
1854 if (checkIfInStructure
&& !isInStructure(element
)) {
1859 if (wasRootNodeInitialized()) {
1860 List
<Object
> kidsToExpand
= new ArrayList
<Object
>();
1861 Object eachElement
= element
;
1862 DefaultMutableTreeNode firstVisible
= null;
1864 if (!isValid(eachElement
)) break;
1866 firstVisible
= getNodeForElement(eachElement
, true);
1867 if (eachElement
!= element
|| !parentsOnly
) {
1868 assert !kidsToExpand
.contains(eachElement
) :
1869 "Not a valid tree structure, walking up the structure gives many entries for element=" +
1872 getTreeStructure().getRootElement();
1873 kidsToExpand
.add(eachElement
);
1875 if (firstVisible
!= null) break;
1876 eachElement
= getTreeStructure().getParentElement(eachElement
);
1877 if (eachElement
== null) {
1878 firstVisible
= null;
1883 if (firstVisible
== null) {
1886 else if (kidsToExpand
.size() == 0) {
1887 final DefaultMutableTreeNode parentNode
= (DefaultMutableTreeNode
)firstVisible
.getParent();
1888 if (parentNode
!= null) {
1889 final TreePath parentPath
= new TreePath(parentNode
.getPath());
1890 if (!myTree
.isExpanded(parentPath
)) {
1897 processExpand(firstVisible
, kidsToExpand
, kidsToExpand
.size() - 1, onDone
);
1901 myDeferredExpansions
.add(new Runnable() {
1903 _expand(element
, onDone
, parentsOnly
, false);
1909 private void processExpand(final DefaultMutableTreeNode toExpand
,
1910 final List kidsToExpand
,
1911 final int expandIndex
,
1912 @NotNull final Runnable onDone
) {
1913 final Object element
= getElementFor(toExpand
);
1914 if (element
== null) return;
1916 addNodeAction(element
, new NodeAction() {
1917 public void onReady(final DefaultMutableTreeNode node
) {
1918 if (node
.getChildCount() >= 0 && !myTree
.isExpanded(new TreePath(node
.getPath()))) {
1922 if (expandIndex
< 0) {
1927 final DefaultMutableTreeNode nextNode
= getNodeForElement(kidsToExpand
.get(expandIndex
), false);
1928 if (nextNode
!= null) {
1929 processExpand(nextNode
, kidsToExpand
, expandIndex
- 1, onDone
);
1942 private Object
getElementFor(Object node
) {
1943 if (!(node
instanceof DefaultMutableTreeNode
)) return null;
1944 return getElementFor((DefaultMutableTreeNode
)node
);
1948 private Object
getElementFor(DefaultMutableTreeNode node
) {
1950 final Object o
= node
.getUserObject();
1951 if (o
instanceof NodeDescriptor
) {
1952 return getElementFromDescriptor(((NodeDescriptor
)o
));
1959 public final boolean isNodeBeingBuilt(final TreePath path
) {
1960 return isNodeBeingBuilt(path
.getLastPathComponent());
1963 public final boolean isNodeBeingBuilt(Object node
) {
1964 if (isParentLoading(node
) || isLoadingParent(node
)) return true;
1966 final boolean childrenAreNoLoadedYet
= isLoadingChildrenFor(node
) && myUnbuiltNodes
.contains(node
);
1967 if (childrenAreNoLoadedYet
) {
1968 if (node
instanceof DefaultMutableTreeNode
) {
1969 final TreePath nodePath
= new TreePath(((DefaultMutableTreeNode
)node
).getPath());
1970 if (!myTree
.isExpanded(nodePath
)) return false;
1980 private boolean isLoadingParent(Object node
) {
1981 if (!(node
instanceof DefaultMutableTreeNode
)) return false;
1982 return myLoadingParents
.contains(getElementFor((DefaultMutableTreeNode
)node
));
1985 public void setTreeStructure(final AbstractTreeStructure treeStructure
) {
1986 myTreeStructure
= treeStructure
;
1987 clearUpdaterState();
1990 public AbstractTreeUpdater
getUpdater() {
1994 public void setUpdater(final AbstractTreeUpdater updater
) {
1995 myUpdater
= updater
;
1998 public DefaultMutableTreeNode
getRootNode() {
2002 public void setRootNode(@NotNull final DefaultMutableTreeNode rootNode
) {
2003 myRootNode
= rootNode
;
2006 private void dropUpdaterStateIfExternalChange() {
2007 if (myUpdaterState
!= null && !myUpdaterState
.isProcessingNow()) {
2008 clearUpdaterState();
2012 private void clearUpdaterState() {
2013 myUpdaterState
= null;
2016 private void createMapping(Object element
, DefaultMutableTreeNode node
) {
2017 if (!myElementToNodeMap
.containsKey(element
)) {
2018 myElementToNodeMap
.put(element
, node
);
2021 final Object value
= myElementToNodeMap
.get(element
);
2022 final List
<DefaultMutableTreeNode
> nodes
;
2023 if (value
instanceof DefaultMutableTreeNode
) {
2024 nodes
= new ArrayList
<DefaultMutableTreeNode
>();
2025 nodes
.add((DefaultMutableTreeNode
)value
);
2026 myElementToNodeMap
.put(element
, nodes
);
2029 nodes
= (List
<DefaultMutableTreeNode
>)value
;
2035 private void removeMapping(Object element
, DefaultMutableTreeNode node
) {
2036 final Object value
= myElementToNodeMap
.get(element
);
2037 if (value
== null) {
2040 if (value
instanceof DefaultMutableTreeNode
) {
2041 if (value
.equals(node
)) {
2042 myElementToNodeMap
.remove(element
);
2046 List
<DefaultMutableTreeNode
> nodes
= (List
<DefaultMutableTreeNode
>)value
;
2047 final boolean reallyRemoved
= nodes
.remove(node
);
2048 if (reallyRemoved
) {
2049 if (nodes
.isEmpty()) {
2050 myElementToNodeMap
.remove(element
);
2056 private DefaultMutableTreeNode
getFirstNode(Object element
) {
2057 return findNode(element
, 0);
2060 private DefaultMutableTreeNode
findNode(final Object element
, int startIndex
) {
2061 final Object value
= getBuilder().findNodeByElement(element
);
2062 if (value
== null) {
2065 if (value
instanceof DefaultMutableTreeNode
) {
2066 return startIndex
== 0 ?
(DefaultMutableTreeNode
)value
: null;
2068 final List
<DefaultMutableTreeNode
> nodes
= (List
<DefaultMutableTreeNode
>)value
;
2069 return startIndex
< nodes
.size() ? nodes
.get(startIndex
) : null;
2072 protected Object
findNodeByElement(Object element
) {
2073 if (myElementToNodeMap
.containsKey(element
)) {
2074 return myElementToNodeMap
.get(element
);
2078 TREE_NODE_WRAPPER
.setValue(element
);
2079 return myElementToNodeMap
.get(TREE_NODE_WRAPPER
);
2082 TREE_NODE_WRAPPER
.setValue(null);
2086 private DefaultMutableTreeNode
findNodeForChildElement(DefaultMutableTreeNode parentNode
, Object element
) {
2087 final Object value
= myElementToNodeMap
.get(element
);
2088 if (value
== null) {
2092 if (value
instanceof DefaultMutableTreeNode
) {
2093 final DefaultMutableTreeNode elementNode
= (DefaultMutableTreeNode
)value
;
2094 return parentNode
.equals(elementNode
.getParent()) ? elementNode
: null;
2097 final List
<DefaultMutableTreeNode
> allNodesForElement
= (List
<DefaultMutableTreeNode
>)value
;
2098 for (final DefaultMutableTreeNode elementNode
: allNodesForElement
) {
2099 if (parentNode
.equals(elementNode
.getParent())) {
2107 public void cancelBackgroundLoading() {
2108 if (myWorker
!= null) {
2109 myWorker
.cancelTasks();
2111 myNodeActions
.clear();
2114 private void addNodeAction(Object element
, NodeAction action
) {
2115 maybeSetBusyAndScheduleWaiterForReady(true);
2117 List
<NodeAction
> list
= myNodeActions
.get(element
);
2119 list
= new ArrayList
<NodeAction
>();
2120 myNodeActions
.put(element
, list
);
2125 private void cleanUpNow() {
2126 if (isReleased()) return;
2128 final UpdaterTreeState state
= new UpdaterTreeState(this);
2130 myTree
.collapsePath(new TreePath(myTree
.getModel().getRoot()));
2131 myTree
.clearSelection();
2132 getRootNode().removeAllChildren();
2134 myRootNodeWasInitialized
= false;
2135 myNodeActions
.clear();
2136 myElementToNodeMap
.clear();
2137 myDeferredSelections
.clear();
2138 myDeferredExpansions
.clear();
2139 myLoadingParents
.clear();
2140 myUnbuiltNodes
.clear();
2141 myUpdateFromRootRequested
= true;
2143 if (myWorker
!= null) {
2144 Disposer
.dispose(myWorker
);
2148 myTree
.invalidate();
2153 public AbstractTreeUi
setClearOnHideDelay(final long clearOnHideDelay
) {
2154 myClearOnHideDelay
= clearOnHideDelay
;
2158 public void setJantorPollPeriod(final long time
) {
2159 myJanitorPollPeriod
= time
;
2162 public void setCheckStructure(final boolean checkStructure
) {
2163 myCheckStructure
= checkStructure
;
2166 private class MySelectionListener
implements TreeSelectionListener
{
2167 public void valueChanged(final TreeSelectionEvent e
) {
2168 dropUpdaterStateIfExternalChange();
2172 private class MyExpansionListener
implements TreeExpansionListener
{
2173 public void treeExpanded(TreeExpansionEvent event
) {
2174 dropUpdaterStateIfExternalChange();
2176 TreePath path
= event
.getPath();
2177 final DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)path
.getLastPathComponent();
2178 if (!myUnbuiltNodes
.contains(node
)) return;
2179 myUnbuiltNodes
.remove(node
);
2181 getBuilder().expandNodeChildren(node
);
2183 runDone(new Runnable() {
2185 final Object element
= getElementFor(node
);
2187 for (int i
= 0; i
< node
.getChildCount(); i
++) {
2188 removeIfLoading(node
.getChildAt(i
));
2191 if (node
.getChildCount() == 0) {
2192 addNodeAction(element
, new NodeAction() {
2193 public void onReady(final DefaultMutableTreeNode node
) {
2194 expand(element
, null);
2199 processSmartExpand(node
);
2204 public void treeCollapsed(TreeExpansionEvent e
) {
2205 final TreePath path
= e
.getPath();
2206 final DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)path
.getLastPathComponent();
2207 if (!(node
.getUserObject() instanceof NodeDescriptor
)) return;
2209 TreePath pathToSelect
= null;
2210 if (isSelectionInside(node
)) {
2211 pathToSelect
= new TreePath(node
.getPath());
2215 NodeDescriptor descriptor
= (NodeDescriptor
)node
.getUserObject();
2216 if (getBuilder().isDisposeOnCollapsing(descriptor
)) {
2217 removeChildren(node
);
2218 addLoadingNode(node
);
2219 if (node
.equals(getRootNode())) {
2220 addSelectionPath(new TreePath(getRootNode().getPath()), true, Condition
.FALSE
);
2223 myTreeModel
.reload(node
);
2227 if (pathToSelect
!= null && myTree
.isSelectionEmpty()) {
2228 addSelectionPath(pathToSelect
, true, Condition
.FALSE
);
2232 private void removeChildren(DefaultMutableTreeNode node
) {
2233 EnumerationCopy copy
= new EnumerationCopy(node
.children());
2234 while (copy
.hasMoreElements()) {
2235 disposeNode((DefaultMutableTreeNode
)copy
.nextElement());
2237 node
.removeAllChildren();
2238 myTreeModel
.nodeStructureChanged(node
);
2241 private boolean isSelectionInside(DefaultMutableTreeNode parent
) {
2242 TreePath path
= new TreePath(myTreeModel
.getPathToRoot(parent
));
2243 TreePath
[] paths
= myTree
.getSelectionPaths();
2244 if (paths
== null) return false;
2245 for (TreePath path1
: paths
) {
2246 if (path
.isDescendant(path1
)) return true;
2252 public boolean isInStructure(@Nullable Object element
) {
2253 Object eachParent
= element
;
2254 while (eachParent
!= null) {
2255 if (getTreeStructure().getRootElement().equals(eachParent
)) return true;
2256 eachParent
= getTreeStructure().getParentElement(eachParent
);
2262 interface NodeAction
{
2263 void onReady(DefaultMutableTreeNode node
);
2266 public void setCanYield(final boolean canYield
) {
2267 myCanYield
= canYield
;
2270 public Collection
<TreeUpdatePass
> getYeildingPasses() {
2271 return myYeildingPasses
;
2274 public boolean isBuilt(Object element
) {
2275 if (!myElementToNodeMap
.containsKey(element
)) return false;
2276 final Object node
= myElementToNodeMap
.get(element
);
2277 return !myUnbuiltNodes
.contains(node
);