TreeUi: isAwlaysLeaf
[fedora-idea.git] / platform / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUi.java
blob6c6db1c67483fe34f63bd27cbb66d9b9b4bc8cb1
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ide.util.treeView;
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.openapi.application.Application;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.progress.ProcessCanceledException;
23 import com.intellij.openapi.progress.ProgressIndicator;
24 import com.intellij.openapi.progress.ProgressManager;
25 import com.intellij.openapi.project.IndexNotReadyException;
26 import com.intellij.openapi.util.*;
27 import com.intellij.openapi.util.registry.Registry;
28 import com.intellij.openapi.util.registry.RegistryValue;
29 import com.intellij.ui.LoadingNode;
30 import com.intellij.ui.treeStructure.Tree;
31 import com.intellij.util.Alarm;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.ConcurrencyUtil;
34 import com.intellij.util.Time;
35 import com.intellij.util.concurrency.WorkerThread;
36 import com.intellij.util.containers.HashSet;
37 import com.intellij.util.enumeration.EnumerationCopy;
38 import com.intellij.util.ui.UIUtil;
39 import com.intellij.util.ui.tree.TreeUtil;
40 import com.intellij.util.ui.update.Activatable;
41 import com.intellij.util.ui.update.UiNotifyConnector;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
45 import javax.swing.*;
46 import javax.swing.event.TreeExpansionEvent;
47 import javax.swing.event.TreeExpansionListener;
48 import javax.swing.event.TreeSelectionEvent;
49 import javax.swing.event.TreeSelectionListener;
50 import javax.swing.tree.*;
51 import java.awt.*;
52 import java.awt.event.FocusAdapter;
53 import java.awt.event.FocusEvent;
54 import java.security.AccessControlException;
55 import java.util.*;
56 import java.util.List;
57 import java.util.concurrent.ScheduledExecutorService;
58 import java.util.concurrent.TimeUnit;
60 public class AbstractTreeUi {
61 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
62 protected JTree myTree;// protected for TestNG
63 @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel;
64 private AbstractTreeStructure myTreeStructure;
65 private AbstractTreeUpdater myUpdater;
67 private Comparator<NodeDescriptor> myNodeDescriptorComparator;
68 private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() {
69 public int compare(TreeNode n1, TreeNode n2) {
70 if (isLoadingNode(n1) || isLoadingNode(n2)) return 0;
71 NodeDescriptor nodeDescriptor1 = getDescriptorFrom(((DefaultMutableTreeNode)n1));
72 NodeDescriptor nodeDescriptor2 = getDescriptorFrom(((DefaultMutableTreeNode)n2));
73 return myNodeDescriptorComparator != null
74 ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2)
75 : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex();
78 long myOwnComparatorStamp;
79 long myLastComparatorStamp;
81 private DefaultMutableTreeNode myRootNode;
82 private final HashMap<Object, Object> myElementToNodeMap = new HashMap<Object, Object>();
83 private final HashSet<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>();
84 private TreeExpansionListener myExpansionListener;
85 private MySelectionListener mySelectionListener;
87 private WorkerThread myWorker = null;
88 private final Set<Runnable> myActiveWorkerTasks = new HashSet<Runnable>();
90 private ProgressIndicator myProgress;
91 private static final int WAIT_CURSOR_DELAY = 100;
92 private AbstractTreeNode<Object> TREE_NODE_WRAPPER;
93 private boolean myRootNodeWasInitialized = false;
94 private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>();
95 private boolean myUpdateFromRootRequested;
96 private boolean myWasEverShown;
97 private boolean myUpdateIfInactive;
99 private final Map<Object, UpdateInfo> myLoadedInBackground = new HashMap<Object, UpdateInfo>();
100 private final Map<Object, List<NodeAction>> myNodeChildrenActions = new HashMap<Object, List<NodeAction>>();
102 private long myClearOnHideDelay = -1;
103 private ScheduledExecutorService ourClearanceService;
104 private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
106 private final Set<Runnable> myDeferredSelections = new HashSet<Runnable>();
107 private final Set<Runnable> myDeferredExpansions = new HashSet<Runnable>();
109 private boolean myCanProcessDeferredSelections;
111 private UpdaterTreeState myUpdaterState;
112 private AbstractTreeBuilder myBuilder;
114 private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
115 private long myJanitorPollPeriod = Time.SECOND * 10;
116 private boolean myCheckStructure = false;
119 private boolean myCanYield = false;
121 private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
123 private boolean myYeildingNow;
125 private final Set<DefaultMutableTreeNode> myPendingNodeActions = new HashSet<DefaultMutableTreeNode>();
126 private final Set<Runnable> myYeildingDoneRunnables = new HashSet<Runnable>();
128 private final Alarm myBusyAlarm = new Alarm();
129 private final Runnable myWaiterForReady = new Runnable() {
130 public void run() {
131 maybeSetBusyAndScheduleWaiterForReady(false);
135 private final RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
136 private final RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
137 private final RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimout");
139 private boolean myWasEverIndexNotReady;
140 private boolean myShowing;
141 private FocusAdapter myFocusListener = new FocusAdapter() {
142 @Override
143 public void focusGained(FocusEvent e) {
144 maybeReady();
147 private Set<DefaultMutableTreeNode> myNotForSmartExpand = new HashSet<DefaultMutableTreeNode>();
148 private TreePath myRequestedExpand;
149 private ActionCallback myInitialized = new ActionCallback();
150 private Map<Object, ActionCallback> myReadyCallbacks = new WeakHashMap<Object, ActionCallback>();
152 private boolean myPassthroughMode = false;
155 protected void init(AbstractTreeBuilder builder,
156 JTree tree,
157 DefaultTreeModel treeModel,
158 AbstractTreeStructure treeStructure,
159 @Nullable Comparator<NodeDescriptor> comparator,
160 boolean updateIfInactive) {
161 myBuilder = builder;
162 myTree = tree;
163 myTreeModel = treeModel;
164 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
165 myTree.setModel(myTreeModel);
166 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
167 setTreeStructure(treeStructure);
168 myNodeDescriptorComparator = comparator;
169 myUpdateIfInactive = updateIfInactive;
171 myExpansionListener = new MyExpansionListener();
172 myTree.addTreeExpansionListener(myExpansionListener);
174 mySelectionListener = new MySelectionListener();
175 myTree.addTreeSelectionListener(mySelectionListener);
177 setUpdater(getBuilder().createUpdater());
178 myProgress = getBuilder().createProgressIndicator();
179 Disposer.register(getBuilder(), getUpdater());
181 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
182 public void showNotify() {
183 myShowing = true;
184 myWasEverShown = true;
185 if (!isReleased()) {
186 activate(true);
190 public void hideNotify() {
191 myShowing = false;
192 if (!isReleased()) {
193 deactivate();
197 Disposer.register(getBuilder(), uiNotify);
199 myTree.addFocusListener(myFocusListener);
203 boolean isNodeActionsPending() {
204 return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty();
207 private void clearNodeActions() {
208 myNodeActions.clear();
209 myNodeChildrenActions.clear();
212 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
213 if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
215 if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
216 final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
217 final boolean isBusy = !isReady() || forcedBusy;
218 if (isBusy && tree.isShowing()) {
219 tree.setPaintBusy(true);
220 myBusyAlarm.cancelAllRequests();
221 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
223 else {
224 tree.setPaintBusy(false);
229 private void initClearanceServiceIfNeeded() {
230 if (ourClearanceService != null) return;
232 ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor");
233 ourClearanceService.scheduleWithFixedDelay(new Runnable() {
234 public void run() {
235 cleanUpAll();
237 }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
240 private void cleanUpAll() {
241 final long now = System.currentTimeMillis();
242 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
243 for (AbstractTreeUi eachUi : uis) {
244 if (eachUi == null) continue;
245 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
246 if (timeToCleanup == null) continue;
247 if (now >= timeToCleanup.longValue()) {
248 ourUi2Countdown.remove(eachUi);
249 Runnable runnable = new Runnable() {
250 public void run() {
251 getBuilder().cleanUp();
254 if (isPassthroughMode()) {
255 runnable.run();
256 } else {
257 UIUtil.invokeAndWaitIfNeeded(runnable);
263 protected void doCleanUp() {
264 Runnable cleanup = new Runnable() {
265 public void run() {
266 if (!isReleased()) {
267 cleanUpNow();
272 if (isPassthroughMode()) {
273 cleanup.run();
274 } else {
275 UIUtil.invokeLaterIfNeeded(cleanup);
279 private void disposeClearanceService() {
280 try {
281 if (ourClearanceService != null) {
282 ourClearanceService.shutdown();
283 ourClearanceService = null;
286 catch (AccessControlException e) {
287 LOG.warn(e);
291 public void activate(boolean byShowing) {
292 myCanProcessDeferredSelections = true;
293 ourUi2Countdown.remove(this);
295 if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) {
296 getBuilder().updateFromRoot();
299 getUpdater().showNotify();
301 myWasEverShown |= byShowing;
304 public void deactivate() {
305 getUpdater().hideNotify();
306 myBusyAlarm.cancelAllRequests();
308 if (!myWasEverShown) return;
310 if (isNodeActionsPending()) {
311 cancelBackgroundLoading();
312 myUpdateFromRootRequested = true;
315 if (getClearOnHideDelay() >= 0) {
316 ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
317 initClearanceServiceIfNeeded();
322 public void release() {
323 if (isReleased()) return;
325 myTree.removeTreeExpansionListener(myExpansionListener);
326 myTree.removeTreeSelectionListener(mySelectionListener);
327 myTree.removeFocusListener(myFocusListener);
329 disposeNode(getRootNode());
330 myElementToNodeMap.clear();
331 getUpdater().cancelAllRequests();
332 if (myWorker != null) {
333 myWorker.dispose(true);
334 clearWorkerTasks();
336 TREE_NODE_WRAPPER.setValue(null);
337 if (myProgress != null) {
338 myProgress.cancel();
340 disposeClearanceService();
342 myTree = null;
343 setUpdater(null);
344 myWorker = null;
345 //todo [kirillk] afraid to do so just in release day, to uncomment
346 // myTreeStructure = null;
347 myBuilder = null;
349 clearNodeActions();
351 myDeferredSelections.clear();
352 myDeferredExpansions.clear();
353 myYeildingDoneRunnables.clear();
356 public boolean isReleased() {
357 return myBuilder == null;
360 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
361 getTreeStructure().commit();
362 addSubtreeToUpdate(node);
363 getUpdater().performUpdate();
366 public final AbstractTreeStructure getTreeStructure() {
367 return myTreeStructure;
370 public final JTree getTree() {
371 return myTree;
374 @Nullable
375 private NodeDescriptor getDescriptorFrom(DefaultMutableTreeNode node) {
376 return (NodeDescriptor)node.getUserObject();
379 @Nullable
380 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
381 DefaultMutableTreeNode result = null;
382 if (validateAgainstStructure) {
383 int index = 0;
384 while (true) {
385 final DefaultMutableTreeNode node = findNode(element, index);
386 if (node == null) break;
388 if (isNodeValidForElement(element, node)) {
389 result = node;
390 break;
393 index++;
396 else {
397 result = getFirstNode(element);
401 if (result != null && !isNodeInStructure(result)) {
402 disposeNode(result);
403 result = null;
406 return result;
409 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
410 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
413 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
414 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
417 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
418 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
419 final Object parentElement = getElementFor(parent);
420 if (!isInStructure(parentElement)) return false;
422 if (parent instanceof ElementNode) {
423 return ((ElementNode)parent).isValidChild(element);
425 else {
426 for (int i = 0; i < parent.getChildCount(); i++) {
427 final TreeNode child = parent.getChildAt(i);
428 final Object eachElement = getElementFor(child);
429 if (element.equals(eachElement)) return true;
433 return false;
436 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
437 boolean valid = true;
438 while (true) {
439 if (eachParent == null) {
440 valid = eachParentNode == null;
441 break;
444 if (!eachParent.equals(getElementFor(eachParentNode))) {
445 valid = false;
446 break;
449 eachParent = getTreeStructure().getParentElement(eachParent);
450 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
452 return valid;
455 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
456 DefaultMutableTreeNode node = null;
457 for (final Object pathElement : path) {
458 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
459 if (node == null) {
460 break;
463 return node;
466 public final void buildNodeForElement(Object element) {
467 getUpdater().performUpdate();
468 DefaultMutableTreeNode node = getNodeForElement(element, false);
469 if (node == null) {
470 final java.util.List<Object> elements = new ArrayList<Object>();
471 while (true) {
472 element = getTreeStructure().getParentElement(element);
473 if (element == null) {
474 break;
476 elements.add(0, element);
479 for (final Object element1 : elements) {
480 node = getNodeForElement(element1, false);
481 if (node != null) {
482 expand(node, true);
488 public final void buildNodeForPath(Object[] path) {
489 getUpdater().performUpdate();
490 DefaultMutableTreeNode node = null;
491 for (final Object pathElement : path) {
492 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
493 if (node != null && node != path[path.length - 1]) {
494 expand(node, true);
499 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
500 myNodeDescriptorComparator = nodeDescriptorComparator;
501 myLastComparatorStamp = -1;
502 getBuilder().queueUpdateFrom(getTreeStructure().getRootElement(), true);
505 protected AbstractTreeBuilder getBuilder() {
506 return myBuilder;
509 protected final void initRootNode() {
510 if (myUpdateIfInactive) {
511 activate(false);
513 else {
514 myUpdateFromRootRequested = true;
518 private void initRootNodeNowIfNeeded(final TreeUpdatePass pass) {
519 if (myRootNodeWasInitialized) return;
521 myRootNodeWasInitialized = true;
523 final Object rootElement = getTreeStructure().getRootElement();
524 addNodeAction(rootElement, new NodeAction() {
525 public void onReady(final DefaultMutableTreeNode node) {
526 processDeferredActions();
528 }, false);
531 final Ref<NodeDescriptor> rootDescriptor = new Ref<NodeDescriptor>(null);
532 final boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(rootElement);
534 Runnable build = new Runnable() {
535 public void run() {
536 rootDescriptor.set(getTreeStructure().createDescriptor(rootElement, null));
537 getRootNode().setUserObject(rootDescriptor.get());
538 update(rootDescriptor.get(), true);
543 Runnable update = new Runnable() {
544 public void run() {
545 if (getElementFromDescriptor(rootDescriptor.get()) != null) {
546 createMapping(getElementFromDescriptor(rootDescriptor.get()), getRootNode());
550 insertLoadingNode(getRootNode(), true);
552 boolean willUpdate = false;
553 if (isAutoExpand(rootDescriptor.get())) {
554 willUpdate = myUnbuiltNodes.contains(getRootNode());
555 expand(getRootNode(), true);
557 if (!willUpdate) {
558 updateNodeChildren(getRootNode(), pass, null, false, false, false, true);
560 if (getRootNode().getChildCount() == 0) {
561 myTreeModel.nodeChanged(getRootNode());
566 if (bgLoading) {
567 queueToBackground(build, update, null);
569 else {
570 build.run();
571 update.run();
575 private boolean isAutoExpand(NodeDescriptor descriptor) {
576 boolean autoExpand = false;
578 if (descriptor != null) {
579 autoExpand = getBuilder().isAutoExpandNode(descriptor);
582 if (!autoExpand && !myTree.isRootVisible()) {
583 Object element = getElementFromDescriptor(descriptor);
584 if (element != null && element.equals(getTreeStructure().getRootElement())) return true;
587 return autoExpand;
590 private boolean isAutoExpand(DefaultMutableTreeNode node) {
591 return isAutoExpand(getDescriptorFrom(node));
594 private AsyncResult<Boolean> update(final NodeDescriptor nodeDescriptor, boolean now) {
595 final AsyncResult<Boolean> result = new AsyncResult<Boolean>();
597 if (now || isPassthroughMode()) {
598 return new AsyncResult<Boolean>().setDone(_update(nodeDescriptor));
601 Object element = getElementFromDescriptor(nodeDescriptor);
602 boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(element);
604 boolean edt = isEdt();
605 if (bgLoading) {
606 if (edt) {
607 final Ref<Boolean> changes = new Ref<Boolean>(false);
608 queueToBackground(new Runnable() {
609 public void run() {
610 changes.set(_update(nodeDescriptor));
612 }, new Runnable() {
613 public void run() {
614 result.setDone(changes.get());
616 }, null);
618 else {
619 result.setDone(_update(nodeDescriptor));
622 else {
623 if (edt || !myWasEverShown) {
624 result.setDone(_update(nodeDescriptor));
626 else {
627 UIUtil.invokeLaterIfNeeded(new Runnable() {
628 public void run() {
629 if (!isReleased()) {
630 result.setDone(_update(nodeDescriptor));
632 else {
633 result.setRejected();
640 result.doWhenDone(new AsyncResult.Handler<Boolean>() {
641 public void run(Boolean changes) {
642 if (changes) {
643 final long updateStamp = nodeDescriptor.getUpdateCount();
644 UIUtil.invokeLaterIfNeeded(new Runnable() {
645 public void run() {
646 Object element = nodeDescriptor.getElement();
647 DefaultMutableTreeNode node = getNodeForElement(element, false);
648 if (node != null) {
649 TreePath path = getPathFor(node);
650 if (path != null && myTree.isVisible(path)) {
651 updateNodeImageAndPosition(node, false);
661 return result;
664 private boolean _update(NodeDescriptor nodeDescriptor) {
665 nodeDescriptor.setUpdateCount(nodeDescriptor.getUpdateCount() + 1);
666 return getBuilder().updateNodeDescriptor(nodeDescriptor);
669 private void assertIsDispatchThread() {
670 if (isPassthroughMode()) return;
672 if (isTreeShowing() && !isEdt()) {
673 LOG.error("Must be in event-dispatch thread");
677 private boolean isEdt() {
678 return SwingUtilities.isEventDispatchThread();
681 private boolean isTreeShowing() {
682 return myShowing;
685 private void assertNotDispatchThread() {
686 if (isPassthroughMode()) return;
688 if (isEdt()) {
689 LOG.error("Must not be in event-dispatch thread");
693 private void processDeferredActions() {
694 processDeferredActions(myDeferredSelections);
695 processDeferredActions(myDeferredExpansions);
698 private void processDeferredActions(Set<Runnable> actions) {
699 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
700 actions.clear();
701 for (Runnable runnable : runnables) {
702 runnable.run();
706 //todo: to make real callback
707 public ActionCallback queueUpdate(Object element) {
708 AbstractTreeUpdater updater = getUpdater();
709 if (updater == null) {
710 return new ActionCallback.Rejected();
713 final ActionCallback result = new ActionCallback();
714 DefaultMutableTreeNode node = getNodeForElement(element, false);
715 if (node != null) {
716 addSubtreeToUpdate(node);
718 else {
719 addSubtreeToUpdate(getRootNode());
722 updater.runAfterUpdate(new Runnable() {
723 public void run() {
724 result.setDone();
727 return result;
730 public void doUpdateFromRoot() {
731 updateSubtree(getRootNode(), false);
734 public ActionCallback doUpdateFromRootCB() {
735 final ActionCallback cb = new ActionCallback();
736 getUpdater().runAfterUpdate(new Runnable() {
737 public void run() {
738 cb.setDone();
741 updateSubtree(getRootNode(), false);
742 return cb;
745 public final void updateSubtree(DefaultMutableTreeNode node, boolean canSmartExpand) {
746 updateSubtree(new TreeUpdatePass(node), canSmartExpand);
749 public final void updateSubtree(TreeUpdatePass pass, boolean canSmartExpand) {
750 if (getUpdater() != null) {
751 getUpdater().addSubtreeToUpdate(pass);
753 else {
754 updateSubtreeNow(pass, canSmartExpand);
758 final void updateSubtreeNow(TreeUpdatePass pass, boolean canSmartExpand) {
759 maybeSetBusyAndScheduleWaiterForReady(true);
761 initRootNodeNowIfNeeded(pass);
763 final DefaultMutableTreeNode node = pass.getNode();
765 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
767 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
769 boolean forceUpdate = true;
770 TreePath path = getPathFor(node);
771 boolean invisible = !myTree.isExpanded(path) && (path.getParentPath() == null || !myTree.isExpanded(path.getParentPath()));
773 if (invisible && myUnbuiltNodes.contains(node)) {
774 forceUpdate = false;
777 updateNodeChildren(node, pass, null, false, canSmartExpand, forceUpdate, false);
780 private boolean isToBuildInBackground(NodeDescriptor descriptor) {
781 return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor));
784 @NotNull
785 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
786 final UpdaterTreeState oldState = myUpdaterState;
787 if (oldState == null) {
788 myUpdaterState = state;
789 return state;
791 else {
792 oldState.addAll(state);
793 return oldState;
797 protected void doUpdateNode(final DefaultMutableTreeNode node) {
798 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
799 final NodeDescriptor descriptor = getDescriptorFrom(node);
800 final Object prevElement = getElementFromDescriptor(descriptor);
801 if (prevElement == null) return;
802 update(descriptor, false).doWhenDone(new AsyncResult.Handler<Boolean>() {
803 public void run(Boolean changes) {
804 if (!isValid(descriptor)) {
805 if (isInStructure(prevElement)) {
806 getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement));
807 return;
810 if (changes) {
811 updateNodeImageAndPosition(node, true);
817 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
818 return getBuilder().getTreeStructureElement(descriptor);
821 private void updateNodeChildren(final DefaultMutableTreeNode node,
822 final TreeUpdatePass pass,
823 @Nullable LoadedChildren loadedChildren,
824 boolean forcedNow,
825 final boolean toSmartExpand,
826 boolean forceUpdate,
827 final boolean descriptorIsUpToDate) {
828 getTreeStructure().commit();
829 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node);
830 final boolean wasLeaf = node.getChildCount() == 0;
832 try {
833 final NodeDescriptor descriptor = getDescriptorFrom(node);
834 if (descriptor == null) {
835 removeLoading(node, true);
836 return;
839 boolean bgBuild = isToBuildInBackground(descriptor);
840 boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded;
842 if (notRequiredToUpdateChildren && forceUpdate && !wasExpanded) {
843 boolean alwaysPlus = getBuilder().isAlwaysShowPlus(descriptor);
844 if (alwaysPlus && wasLeaf) {
845 notRequiredToUpdateChildren = false;
846 } else {
847 notRequiredToUpdateChildren = alwaysPlus;
851 final Ref<LoadedChildren> preloaded = new Ref<LoadedChildren>(loadedChildren);
852 boolean descriptorWasUpdated = descriptorIsUpToDate;
854 if (notRequiredToUpdateChildren) {
855 if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) {
856 insertLoadingNode(node, true);
858 return;
861 if (!forcedNow) {
862 if (!bgBuild) {
863 if (myUnbuiltNodes.contains(node)) {
864 if (!descriptorWasUpdated) {
865 update(descriptor, true);
866 descriptorWasUpdated = true;
869 if (processAlwaysLeaf(node)) return;
871 Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null);
872 if (unbuilt.getFirst()) return;
873 preloaded.set(unbuilt.getSecond());
879 final boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded);
881 if (!forcedNow && isToBuildInBackground(descriptor)) {
882 if (processAlwaysLeaf(node)) return;
884 queueBackgroundUpdate(
885 new UpdateInfo(descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorWasUpdated), node);
886 return;
888 else {
889 if (!descriptorWasUpdated) {
890 update(descriptor, false).doWhenDone(new Runnable() {
891 public void run() {
892 if (processAlwaysLeaf(node)) return;
893 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
897 else {
898 if (processAlwaysLeaf(node)) return;
900 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
904 finally {
905 processNodeActionsIfReady(node);
909 private boolean processAlwaysLeaf(DefaultMutableTreeNode node) {
910 Object element = getElementFor(node);
911 if (getTreeStructure().isAlwaysLeaf(element)) {
912 removeLoading(node, true);
913 processNodeActionsIfReady(node);
914 return true;
915 } else {
916 return false;
920 private boolean isChildNodeForceUpdate(DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) {
921 TreePath path = getPathFor(node);
922 return parentForceUpdate && (parentExpanded || myTree.isExpanded(path));
925 private void updateNodeChildrenNow(final DefaultMutableTreeNode node,
926 final TreeUpdatePass pass,
927 final LoadedChildren preloadedChildren,
928 final boolean toSmartExpand,
929 final boolean wasExpanded,
930 final boolean wasLeaf,
931 final boolean forceUpdate) {
932 final NodeDescriptor descriptor = getDescriptorFrom(node);
934 final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren);
935 final LoadedChildren loadedChildren =
936 preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray());
939 addToUpdating(node);
940 pass.setCurrentNode(node);
942 final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
944 processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren)
945 .doWhenDone(new Runnable() {
946 public void run() {
947 if (isDisposed(node)) {
948 return;
951 removeLoading(node, false);
953 final boolean expanded = isExpanded(node, wasExpanded);
955 collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren)
956 .doWhenDone(new AsyncResult.Handler<ArrayList<TreeNode>>() {
957 public void run(ArrayList<TreeNode> nodesToInsert) {
958 insertNodesInto(nodesToInsert, node);
959 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
960 removeLoading(node, true);
962 if (node.getChildCount() > 0) {
963 if (expanded) {
964 expand(node, canSmartExpand);
968 removeFromUpdating(node);
970 final Object element = getElementFor(node);
971 addNodeAction(element, new NodeAction() {
972 public void onReady(final DefaultMutableTreeNode node) {
973 removeLoading(node, false);
975 }, false);
977 processNodeActionsIfReady(node);
984 private boolean isDisposed(DefaultMutableTreeNode node) {
985 return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
988 private void expand(DefaultMutableTreeNode node, boolean canSmartExpand) {
989 expand(new TreePath(node.getPath()), canSmartExpand);
992 private void expand(final TreePath path, boolean canSmartExpand) {
993 if (path == null) return;
996 final Object last = path.getLastPathComponent();
997 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
998 final boolean isRoot = last == myTree.getModel().getRoot();
999 final TreePath parent = path.getParentPath();
1000 if (isRoot && !myTree.isExpanded(path)) {
1001 if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) {
1002 insertLoadingNode((DefaultMutableTreeNode)last, false);
1004 expandPath(path, canSmartExpand);
1006 else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last))) {
1007 if (last instanceof DefaultMutableTreeNode) {
1008 processNodeActionsIfReady((DefaultMutableTreeNode)last);
1011 else {
1012 if (isLeaf && myUnbuiltNodes.contains(last)) {
1013 insertLoadingNode((DefaultMutableTreeNode)last, true);
1014 expandPath(path, canSmartExpand);
1016 else if (isLeaf && parent != null) {
1017 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
1018 if (parentNode != null) {
1019 addToUnbuilt(parentNode);
1021 expandPath(parent, canSmartExpand);
1023 else {
1024 expandPath(path, canSmartExpand);
1029 private void addToUnbuilt(DefaultMutableTreeNode node) {
1030 myUnbuiltNodes.add(node);
1033 private void removeFromUnbuilt(DefaultMutableTreeNode node) {
1034 myUnbuiltNodes.remove(node);
1037 private Pair<Boolean, LoadedChildren> processUnbuilt(final DefaultMutableTreeNode node,
1038 final NodeDescriptor descriptor,
1039 final TreeUpdatePass pass,
1040 boolean isExpanded,
1041 final LoadedChildren loadedChildren) {
1042 if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) {
1043 return new Pair<Boolean, LoadedChildren>(true, null);
1046 final Object element = getElementFor(node);
1048 final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element));
1050 boolean processed;
1052 if (children.getElements().size() == 0) {
1053 removeLoading(node, true);
1054 processed = true;
1056 else {
1057 if (isAutoExpand(node)) {
1058 addNodeAction(getElementFor(node), new NodeAction() {
1059 public void onReady(final DefaultMutableTreeNode node) {
1060 final TreePath path = new TreePath(node.getPath());
1061 if (getTree().isExpanded(path) || children.getElements().size() == 0) {
1062 removeLoading(node, false);
1064 else {
1065 maybeYeild(new ActiveRunnable() {
1066 public ActionCallback run() {
1067 expand(element, null);
1068 return new ActionCallback.Done();
1070 }, pass, node);
1073 }, false);
1075 processed = false;
1078 processNodeActionsIfReady(node);
1080 return new Pair<Boolean, LoadedChildren>(processed, children);
1083 private boolean removeIfLoading(TreeNode node) {
1084 if (isLoadingNode(node)) {
1085 moveSelectionToParentIfNeeded(node);
1086 removeNodeFromParent((MutableTreeNode)node, false);
1087 return true;
1090 return false;
1093 private void moveSelectionToParentIfNeeded(TreeNode node) {
1094 TreePath path = getPathFor(node);
1095 if (myTree.getSelectionModel().isPathSelected(path)) {
1096 TreePath parentPath = path.getParentPath();
1097 myTree.getSelectionModel().removeSelectionPath(path);
1098 if (parentPath != null) {
1099 myTree.getSelectionModel().addSelectionPath(parentPath);
1104 //todo [kirillk] temporary consistency check
1105 private Object[] getChildrenFor(final Object element) {
1106 final Object[] passOne;
1107 try {
1108 passOne = getTreeStructure().getChildElements(element);
1110 catch (IndexNotReadyException e) {
1111 if (!myWasEverIndexNotReady) {
1112 myWasEverIndexNotReady = true;
1113 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
1115 return ArrayUtil.EMPTY_OBJECT_ARRAY;
1118 if (!myCheckStructure) return passOne;
1120 final Object[] passTwo = getTreeStructure().getChildElements(element);
1122 final HashSet two = new HashSet(Arrays.asList(passTwo));
1124 if (passOne.length != passTwo.length) {
1125 LOG.error(
1126 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1127 element);
1129 else {
1130 for (Object eachInOne : passOne) {
1131 if (!two.contains(eachInOne)) {
1132 LOG.error(
1133 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1134 element);
1135 break;
1140 return passOne;
1143 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert,
1144 TreeUpdatePass pass,
1145 boolean canSmartExpand,
1146 boolean forceUpdate) {
1147 for (TreeNode aNodesToInsert : nodesToInsert) {
1148 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
1149 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
1153 private ActionCallback processExistingNodes(final DefaultMutableTreeNode node,
1154 final MutualMap<Object, Integer> elementToIndexMap,
1155 final TreeUpdatePass pass,
1156 final boolean canSmartExpand,
1157 final boolean forceUpdate,
1158 final boolean wasExpaned,
1159 final LoadedChildren preloaded) {
1161 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
1162 return maybeYeild(new ActiveRunnable() {
1163 public ActionCallback run() {
1164 if (pass.isExpired()) return new ActionCallback.Rejected();
1165 if (childNodes.size() == 0) return new ActionCallback.Done();
1168 final ActionCallback result = new ActionCallback(childNodes.size());
1170 for (TreeNode each : childNodes) {
1171 final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each;
1172 if (isLoadingNode(eachChild)) {
1173 result.setDone();
1174 continue;
1177 final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned);
1179 maybeYeild(new ActiveRunnable() {
1180 @Override
1181 public ActionCallback run() {
1182 return processExistingNode(eachChild, getDescriptorFrom(eachChild), node, elementToIndexMap, pass, canSmartExpand,
1183 childForceUpdate, preloaded);
1185 }, pass, node).notify(result);
1187 if (result.isRejected()) {
1188 break;
1192 return result;
1194 }, pass, node);
1197 private boolean isRerunNeeded(TreeUpdatePass pass) {
1198 if (pass.isExpired()) return false;
1200 final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
1202 return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
1205 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
1206 final ActionCallback result = new ActionCallback();
1208 if (isRerunNeeded(pass)) {
1209 getUpdater().addSubtreeToUpdate(pass);
1210 result.setRejected();
1212 else {
1213 if (isToYieldUpdateFor(node)) {
1214 pass.setCurrentNode(node);
1215 yieldAndRun(new Runnable() {
1216 public void run() {
1217 if (pass.isExpired()) return;
1219 if (isRerunNeeded(pass)) {
1220 runDone(new Runnable() {
1221 public void run() {
1222 if (!pass.isExpired()) {
1223 getUpdater().addSubtreeToUpdate(pass);
1227 result.setRejected();
1229 else {
1230 processRunnable.run().notify(result);
1233 }, pass);
1235 else {
1236 processRunnable.run().notify(result);
1240 return result;
1243 private void yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
1244 myYeildingPasses.add(pass);
1245 myYeildingNow = true;
1246 yield(new Runnable() {
1247 public void run() {
1248 if (isReleased()) {
1249 return;
1252 runOnYieldingDone(new Runnable() {
1253 public void run() {
1254 if (isReleased()) {
1255 return;
1257 executeYieldingRequest(runnable, pass);
1264 public boolean isYeildingNow() {
1265 return myYeildingNow;
1268 private boolean hasSheduledUpdates() {
1269 return getUpdater().hasNodesToUpdate() || isLoadingInBackgroundNow();
1272 public boolean isReady() {
1273 return isIdle() && !hasPendingWork() && !isNodeActionsPending();
1276 public boolean hasPendingWork() {
1277 return hasNodesToUpdate() || (myUpdaterState != null && myUpdaterState.isProcessingNow());
1280 public boolean isIdle() {
1281 return !isYeildingNow() && !isWorkerBusy() && (!hasSheduledUpdates() || getUpdater().isInPostponeMode());
1284 private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
1285 try {
1286 myYeildingPasses.remove(pass);
1287 runnable.run();
1289 finally {
1290 maybeYeildingFinished();
1294 private void maybeYeildingFinished() {
1295 if (myYeildingPasses.size() == 0) {
1296 myYeildingNow = false;
1297 flushPendingNodeActions();
1301 void maybeReady() {
1302 if (isReleased()) return;
1304 if (isReady()) {
1305 if (myTree.isShowing() || myUpdateIfInactive) {
1306 myInitialized.setDone();
1309 if (myTree.isShowing()) {
1310 if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
1311 TreeUtil.ensureSelection(myTree);
1315 if (myInitialized.isDone()) {
1316 for (ActionCallback each : getReadyCallbacks(true)) {
1317 each.setDone();
1323 private void flushPendingNodeActions() {
1324 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
1325 myPendingNodeActions.clear();
1327 for (DefaultMutableTreeNode each : nodes) {
1328 processNodeActionsIfReady(each);
1331 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
1332 for (Runnable each : actions) {
1333 if (!isYeildingNow()) {
1334 myYeildingDoneRunnables.remove(each);
1335 each.run();
1339 maybeReady();
1342 protected void runOnYieldingDone(Runnable onDone) {
1343 getBuilder().runOnYeildingDone(onDone);
1346 protected void yield(Runnable runnable) {
1347 getBuilder().yield(runnable);
1350 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
1351 if (!canYield()) return false;
1352 return getBuilder().isToYieldUpdateFor(node);
1355 private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor,
1356 @Nullable LoadedChildren preloadedChildren) {
1357 MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
1358 List children = preloadedChildren != null
1359 ? preloadedChildren.getElements()
1360 : Arrays.asList(getChildrenFor(getBuilder().getTreeStructureElement(descriptor)));
1361 int index = 0;
1362 for (Object child : children) {
1363 if (!isValid(child)) continue;
1364 elementToIndexMap.put(child, Integer.valueOf(index));
1365 index++;
1367 return elementToIndexMap;
1370 private void expand(final DefaultMutableTreeNode node,
1371 final NodeDescriptor descriptor,
1372 final boolean wasLeaf,
1373 final boolean canSmartExpand) {
1374 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1375 alarm.addRequest(new Runnable() {
1376 public void run() {
1377 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1379 }, WAIT_CURSOR_DELAY);
1381 if (wasLeaf && isAutoExpand(descriptor)) {
1382 expand(node, canSmartExpand);
1385 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1386 for (TreeNode node1 : nodes) {
1387 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1388 if (isLoadingNode(childNode)) continue;
1389 NodeDescriptor childDescr = getDescriptorFrom(childNode);
1390 if (isAutoExpand(childDescr)) {
1391 addNodeAction(getElementFor(childNode), new NodeAction() {
1392 public void onReady(DefaultMutableTreeNode node) {
1393 expand(childNode, canSmartExpand);
1395 }, false);
1396 addSubtreeToUpdate(childNode);
1400 int n = alarm.cancelAllRequests();
1401 if (n == 0) {
1402 myTree.setCursor(Cursor.getDefaultCursor());
1406 public static boolean isLoadingNode(final Object node) {
1407 return node instanceof LoadingNode;
1410 private AsyncResult<ArrayList<TreeNode>> collectNodesToInsert(final NodeDescriptor descriptor,
1411 final MutualMap<Object, Integer> elementToIndexMap,
1412 final DefaultMutableTreeNode parent,
1413 final boolean addLoadingNode,
1414 @NotNull final LoadedChildren loadedChildren) {
1415 final AsyncResult<ArrayList<TreeNode>> result = new AsyncResult<ArrayList<TreeNode>>();
1417 final ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1418 final Collection<Object> allElements = elementToIndexMap.getKeys();
1420 final ActionCallback processingDone = new ActionCallback(allElements.size());
1422 for (final Object child : allElements) {
1423 Integer index = elementToIndexMap.getValue(child);
1424 final Ref<NodeDescriptor> childDescr = new Ref<NodeDescriptor>(loadedChildren.getDescriptor(child));
1425 boolean needToUpdate = false;
1426 if (childDescr.get() == null) {
1427 childDescr.set(getTreeStructure().createDescriptor(child, descriptor));
1428 needToUpdate = true;
1431 //noinspection ConstantConditions
1432 if (childDescr.get() == null) {
1433 LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1434 processingDone.setDone();
1435 continue;
1437 childDescr.get().setIndex(index.intValue());
1439 final ActionCallback update = new ActionCallback();
1440 if (needToUpdate) {
1441 update(childDescr.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1442 public void run(Boolean changes) {
1443 loadedChildren.putDescriptor(child, childDescr.get(), changes);
1444 update.setDone();
1448 else {
1449 update.setDone();
1452 update.doWhenDone(new Runnable() {
1453 public void run() {
1454 Object element = getElementFromDescriptor(childDescr.get());
1455 if (element == null) {
1456 processingDone.setDone();
1457 LOG.error("childDescr.getElement() == null, child = " + child + ", builder = " + this);
1459 else {
1460 DefaultMutableTreeNode node = getNodeForElement(element, false);
1461 if (node == null || node.getParent() != parent) {
1462 final DefaultMutableTreeNode childNode = createChildNode(childDescr.get());
1463 if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr.get())) {
1464 insertLoadingNode(childNode, true);
1466 else {
1467 addToUnbuilt(childNode);
1469 nodesToInsert.add(childNode);
1470 createMapping(element, childNode);
1472 processingDone.setDone();
1478 processingDone.doWhenDone(new Runnable() {
1479 public void run() {
1480 result.setDone(nodesToInsert);
1484 return result;
1487 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1488 return new ElementNode(this, descriptor);
1491 protected boolean canYield() {
1492 return myCanYield && myYeildingUpdate.asBoolean();
1495 public long getClearOnHideDelay() {
1496 return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
1499 public ActionCallback getInitialized() {
1500 return myInitialized;
1503 public ActionCallback getReady(Object requestor) {
1504 if (isReady()) {
1505 return new ActionCallback.Done();
1507 else {
1508 return addReadyCallback(requestor);
1512 private void addToUpdating(DefaultMutableTreeNode node) {
1513 synchronized (myUpdatingChildren) {
1514 myUpdatingChildren.add(node);
1518 private void removeFromUpdating(DefaultMutableTreeNode node) {
1519 synchronized (myUpdatingChildren) {
1520 myUpdatingChildren.remove(node);
1524 public boolean isUpdatingNow(DefaultMutableTreeNode node) {
1525 synchronized (myUpdatingChildren) {
1526 return myUpdatingChildren.contains(node);
1530 boolean hasUpdatingNow() {
1531 synchronized (myUpdatingChildren) {
1532 return myUpdatingChildren.size() > 0;
1536 public Map getNodeActions() {
1537 return myNodeActions;
1540 public List<Object> getLoadedChildrenFor(Object element) {
1541 List<Object> result = new ArrayList<Object>();
1543 DefaultMutableTreeNode node = (DefaultMutableTreeNode)findNodeByElement(element);
1544 if (node != null) {
1545 for (int i = 0; i < node.getChildCount(); i++) {
1546 TreeNode each = node.getChildAt(i);
1547 if (isLoadingNode(each)) continue;
1549 result.add(getElementFor(each));
1553 return result;
1556 public boolean hasNodesToUpdate() {
1557 return getUpdater().hasNodesToUpdate() || hasUpdatingNow() || isLoadingInBackgroundNow();
1560 public List<Object> getExpandedElements() {
1561 List<Object> result = new ArrayList<Object>();
1562 Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode()));
1563 while (enumeration.hasMoreElements()) {
1564 TreePath each = enumeration.nextElement();
1565 Object eachElement = getElementFor(each.getLastPathComponent());
1566 if (eachElement != null) {
1567 result.add(eachElement);
1571 return result;
1574 static class ElementNode extends DefaultMutableTreeNode {
1576 Set<Object> myElements = new HashSet<Object>();
1577 AbstractTreeUi myUi;
1579 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1580 super(descriptor);
1581 myUi = ui;
1584 @Override
1585 public void insert(final MutableTreeNode newChild, final int childIndex) {
1586 super.insert(newChild, childIndex);
1587 final Object element = myUi.getElementFor(newChild);
1588 if (element != null) {
1589 myElements.add(element);
1593 @Override
1594 public void remove(final int childIndex) {
1595 final TreeNode node = getChildAt(childIndex);
1596 super.remove(childIndex);
1597 final Object element = myUi.getElementFor(node);
1598 if (element != null) {
1599 myElements.remove(element);
1603 boolean isValidChild(Object childElement) {
1604 return myElements.contains(childElement);
1607 @Override
1608 public String toString() {
1609 return String.valueOf(getUserObject());
1613 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1614 DefaultMutableTreeNode eachParent = kid;
1615 while (eachParent != null) {
1616 if (isUpdatingNow(eachParent)) return true;
1617 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1620 return false;
1623 private boolean isLoadedInBackground(Object element) {
1624 return getLoadedInBackground(element) != null;
1627 private UpdateInfo getLoadedInBackground(Object element) {
1628 synchronized (myLoadedInBackground) {
1629 return myLoadedInBackground.get(element);
1633 private void addToLoadedInBackground(Object element, UpdateInfo info) {
1634 synchronized (myLoadedInBackground) {
1635 myLoadedInBackground.put(element, info);
1639 private void removeFromLoadedInBackground(final Object element) {
1640 synchronized (myLoadedInBackground) {
1641 myLoadedInBackground.remove(element);
1645 private boolean isLoadingInBackgroundNow() {
1646 synchronized (myLoadedInBackground) {
1647 return myLoadedInBackground.size() > 0;
1651 private boolean queueBackgroundUpdate(final UpdateInfo updateInfo, final DefaultMutableTreeNode node) {
1652 assertIsDispatchThread();
1654 final Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor());
1656 UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor);
1657 if (loaded != null) {
1658 loaded.apply(updateInfo);
1659 return false;
1662 addToLoadedInBackground(oldElementFromDescriptor, updateInfo);
1664 if (!isNodeBeingBuilt(node)) {
1665 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1666 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1669 final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
1670 final Ref<Object> elementFromDescriptor = new Ref<Object>();
1671 Runnable buildRunnable = new Runnable() {
1672 public void run() {
1673 if (isReleased()) {
1674 return;
1677 if (!updateInfo.isDescriptorIsUpToDate()) {
1678 update(updateInfo.getDescriptor(), true);
1681 Object element = getElementFromDescriptor(updateInfo.getDescriptor());
1682 if (element == null) {
1683 removeFromLoadedInBackground(oldElementFromDescriptor);
1684 return;
1687 elementFromDescriptor.set(element);
1689 Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(updateInfo.getDescriptor()));
1690 LoadedChildren loaded = new LoadedChildren(loadedElements);
1691 for (Object each : loadedElements) {
1692 NodeDescriptor eachChildDescriptor = getTreeStructure().createDescriptor(each, updateInfo.getDescriptor());
1693 loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).getResult());
1696 children.set(loaded);
1700 final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
1701 Runnable updateRunnable = new Runnable() {
1702 public void run() {
1703 if (isReleased()) return;
1704 if (children.get() == null) return;
1706 if (isRerunNeeded(updateInfo.getPass())) {
1707 removeFromLoadedInBackground(elementFromDescriptor.get());
1708 getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1709 return;
1712 removeFromLoadedInBackground(elementFromDescriptor.get());
1714 if (myUnbuiltNodes.contains(node)) {
1715 Pair<Boolean, LoadedChildren> unbuilt =
1716 processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()),
1717 children.get());
1718 if (unbuilt.getFirst()) {
1719 nodeToProcessActions[0] = node;
1720 return;
1724 updateNodeChildren(node, updateInfo.getPass(), children.get(), true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(),
1725 true);
1728 if (isRerunNeeded(updateInfo.getPass())) {
1729 getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1730 return;
1733 Object element = elementFromDescriptor.get();
1735 if (element != null) {
1736 removeLoading(node, true);
1737 nodeToProcessActions[0] = node;
1741 queueToBackground(buildRunnable, updateRunnable, new Runnable() {
1742 public void run() {
1743 if (nodeToProcessActions[0] != null) {
1744 processNodeActionsIfReady(nodeToProcessActions[0]);
1748 return true;
1751 private boolean isExpanded(DefaultMutableTreeNode node, boolean isExpanded) {
1752 return isExpanded || myTree.isExpanded(getPathFor(node));
1755 private void removeLoading(DefaultMutableTreeNode parent, boolean removeFromUnbuilt) {
1756 for (int i = 0; i < parent.getChildCount(); i++) {
1757 TreeNode child = parent.getChildAt(i);
1758 if (removeIfLoading(child)) {
1759 i--;
1763 if (removeFromUnbuilt) {
1764 removeFromUnbuilt(parent);
1767 if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
1768 insertLoadingNode(parent, false);
1771 maybeReady();
1774 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1775 if (isNodeBeingBuilt(node)) return;
1777 final Object o = node.getUserObject();
1778 if (!(o instanceof NodeDescriptor)) return;
1781 if (isYeildingNow()) {
1782 myPendingNodeActions.add(node);
1783 return;
1786 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1788 boolean childrenReady = !isLoadedInBackground(element);
1790 processActions(node, element, myNodeActions, childrenReady ? myNodeChildrenActions : null);
1791 if (childrenReady) {
1792 processActions(node, element, myNodeChildrenActions, null);
1795 if (!isUpdatingParent(node) && !isWorkerBusy()) {
1796 final UpdaterTreeState state = myUpdaterState;
1797 if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1798 if (!state.restore(childrenReady ? node : null)) {
1799 setUpdaterState(state);
1804 maybeReady();
1808 private void processActions(DefaultMutableTreeNode node, Object element, final Map<Object, List<NodeAction>> nodeActions, @Nullable final Map<Object, List<NodeAction>> secondaryNodeAction) {
1809 final List<NodeAction> actions = nodeActions.get(element);
1810 if (actions != null) {
1811 nodeActions.remove(element);
1813 List<NodeAction> secondary = secondaryNodeAction != null ? secondaryNodeAction.get(element) : null;
1814 for (NodeAction each : actions) {
1815 if (secondary != null && secondary.contains(each)) {
1816 secondary.remove(each);
1818 each.onReady(node);
1824 private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1825 return !myNotForSmartExpand.contains(node) && canSmartExpand;
1828 private void processSmartExpand(final DefaultMutableTreeNode node, final boolean canSmartExpand) {
1829 if (!getBuilder().isSmartExpand() || !canSmartExpand(node, canSmartExpand)) return;
1831 if (isNodeBeingBuilt(node)) {
1832 addNodeAction(getElementFor(node), new NodeAction() {
1833 public void onReady(DefaultMutableTreeNode node) {
1834 processSmartExpand(node, canSmartExpand);
1836 }, true);
1838 else {
1839 TreeNode child = getChildForSmartExpand(node);
1840 if (child != null) {
1841 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child);
1842 myTree.expandPath(childPath);
1847 @Nullable
1848 private TreeNode getChildForSmartExpand(DefaultMutableTreeNode node) {
1849 int realChildCount = 0;
1850 TreeNode nodeToExpand = null;
1852 for (int i = 0; i < node.getChildCount(); i++) {
1853 TreeNode eachChild = node.getChildAt(i);
1855 if (!isLoadingNode(eachChild)) {
1856 realChildCount++;
1857 if (nodeToExpand == null) {
1858 nodeToExpand = eachChild;
1862 if (realChildCount > 1) {
1863 nodeToExpand = null;
1864 break;
1868 return nodeToExpand;
1871 public boolean isLoadingChildrenFor(final Object nodeObject) {
1872 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1874 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1876 int loadingNodes = 0;
1877 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
1878 TreeNode child = node.getChildAt(i);
1879 if (isLoadingNode(child)) {
1880 loadingNodes++;
1883 return loadingNodes > 0 && loadingNodes == node.getChildCount();
1886 private boolean isParentLoading(Object nodeObject) {
1887 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1889 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1891 TreeNode eachParent = node.getParent();
1893 while (eachParent != null) {
1894 eachParent = eachParent.getParent();
1895 if (eachParent instanceof DefaultMutableTreeNode) {
1896 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
1897 if (isLoadedInBackground(eachElement)) return true;
1901 return false;
1904 protected String getLoadingNodeText() {
1905 return IdeBundle.message("progress.searching");
1908 private ActionCallback processExistingNode(final DefaultMutableTreeNode childNode,
1909 final NodeDescriptor childDescriptor,
1910 final DefaultMutableTreeNode parentNode,
1911 final MutualMap<Object, Integer> elementToIndexMap,
1912 final TreeUpdatePass pass,
1913 final boolean canSmartExpand,
1914 final boolean forceUpdate,
1915 LoadedChildren parentPreloadedChildren) {
1917 final ActionCallback result = new ActionCallback();
1919 if (pass.isExpired()) {
1920 return new ActionCallback.Rejected();
1923 final Ref<NodeDescriptor> childDesc = new Ref<NodeDescriptor>(childDescriptor);
1925 if (childDesc.get() == null) {
1926 pass.expire();
1927 return new ActionCallback.Rejected();
1929 final Object oldElement = getElementFromDescriptor(childDesc.get());
1930 if (oldElement == null) {
1931 pass.expire();
1932 return new ActionCallback.Rejected();
1935 AsyncResult<Boolean> update = new AsyncResult<Boolean>();
1936 if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) {
1937 update.setDone(parentPreloadedChildren.isUpdated(oldElement));
1939 else {
1940 update = update(childDesc.get(), false);
1943 update.doWhenDone(new AsyncResult.Handler<Boolean>() {
1944 public void run(Boolean isChanged) {
1945 final Ref<Boolean> changes = new Ref<Boolean>(isChanged);
1947 final Ref<Boolean> forceRemapping = new Ref<Boolean>(false);
1948 final Ref<Object> newElement = new Ref<Object>(getElementFromDescriptor(childDesc.get()));
1950 final Integer index = newElement.get() != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc.get())) : null;
1951 final AsyncResult<Boolean> updateIndexDone = new AsyncResult<Boolean>();
1952 final ActionCallback indexReady = new ActionCallback();
1953 if (index != null) {
1954 final Object elementFromMap = elementToIndexMap.getKey(index);
1955 if (elementFromMap != newElement.get() && elementFromMap.equals(newElement.get())) {
1956 if (isInStructure(elementFromMap) && isInStructure(newElement.get())) {
1957 if (parentNode.getUserObject() instanceof NodeDescriptor) {
1958 final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
1959 childDesc.set(getTreeStructure().createDescriptor(elementFromMap, parentDescriptor));
1960 childNode.setUserObject(childDesc.get());
1961 newElement.set(elementFromMap);
1962 forceRemapping.set(true);
1963 update(childDesc.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1964 public void run(Boolean isChanged) {
1965 changes.set(isChanged);
1966 updateIndexDone.setDone(isChanged);
1971 else {
1972 updateIndexDone.setDone(changes.get());
1974 } else {
1975 updateIndexDone.setDone(changes.get());
1978 updateIndexDone.doWhenDone(new Runnable() {
1979 public void run() {
1980 if (childDesc.get().getIndex() != index.intValue()) {
1981 changes.set(true);
1983 childDesc.get().setIndex(index.intValue());
1984 indexReady.setDone();
1988 else {
1989 updateIndexDone.setDone();
1992 updateIndexDone.doWhenDone(new Runnable() {
1993 public void run() {
1994 if (index != null && changes.get()) {
1995 updateNodeImageAndPosition(childNode, false);
1997 if (!oldElement.equals(newElement.get()) | forceRemapping.get()) {
1998 removeMapping(oldElement, childNode, newElement.get());
1999 if (newElement.get() != null) {
2000 createMapping(newElement.get(), childNode);
2004 if (index == null) {
2005 int selectedIndex = -1;
2006 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
2007 selectedIndex = parentNode.getIndex(childNode);
2010 if (childNode.getParent() instanceof DefaultMutableTreeNode) {
2011 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
2012 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
2013 if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
2014 insertLoadingNode(parent, false);
2019 Object disposedElement = getElementFor(childNode);
2021 removeNodeFromParent(childNode, selectedIndex >= 0);
2022 disposeNode(childNode);
2024 adjustSelectionOnChildRemove(parentNode, selectedIndex, disposedElement);
2026 else {
2027 elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc.get()));
2028 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
2031 if (parentNode.equals(getRootNode())) {
2032 myTreeModel.nodeChanged(getRootNode());
2035 result.setDone();
2042 return result;
2045 private void adjustSelectionOnChildRemove(DefaultMutableTreeNode parentNode, int selectedIndex, Object disposedElement) {
2046 DefaultMutableTreeNode node = getNodeForElement(disposedElement, false);
2047 if (node != null && isValidForSelectionAdjusting(node)) {
2048 Object newElement = getElementFor(node);
2049 addSelectionPath(getPathFor(node), true, getExpiredElementCondition(newElement));
2050 return;
2054 if (selectedIndex >= 0) {
2055 if (parentNode.getChildCount() > 0) {
2056 if (parentNode.getChildCount() > selectedIndex) {
2057 TreeNode newChildNode = parentNode.getChildAt(selectedIndex);
2058 if (isValidForSelectionAdjusting(newChildNode)) {
2059 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement));
2062 else {
2063 TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1);
2064 if (isValidForSelectionAdjusting(newChild)) {
2065 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement));
2069 else {
2070 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement));
2075 private boolean isValidForSelectionAdjusting(TreeNode node) {
2076 if (!myTree.isRootVisible() && getRootNode() == node) return false;
2078 if (isLoadingNode(node)) return true;
2080 final Object elementInTree = getElementFor(node);
2081 if (elementInTree == null) return false;
2083 final TreeNode parentNode = node.getParent();
2084 final Object parentElementInTree = getElementFor(parentNode);
2085 if (parentElementInTree == null) return false;
2087 final Object parentElement = getTreeStructure().getParentElement(elementInTree);
2089 return parentElementInTree.equals(parentElement);
2092 public Condition getExpiredElementCondition(final Object element) {
2093 return new Condition() {
2094 public boolean value(final Object o) {
2095 return isInStructure(element);
2100 private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement) {
2101 doWithUpdaterState(new Runnable() {
2102 public void run() {
2103 TreePath toSelect = null;
2105 if (isLoadingNode(path.getLastPathComponent())) {
2106 final TreePath parentPath = path.getParentPath();
2107 if (parentPath != null) {
2108 if (isValidForSelectionAdjusting((TreeNode)parentPath.getLastPathComponent())) {
2109 toSelect = parentPath;
2111 else {
2112 toSelect = null;
2116 else {
2117 toSelect = path;
2120 if (toSelect != null) {
2121 myTree.addSelectionPath(toSelect);
2123 if (isAdjustedSelection && myUpdaterState != null) {
2124 final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
2125 myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement);
2132 private static TreePath getPathFor(TreeNode node) {
2133 if (node instanceof DefaultMutableTreeNode) {
2134 return new TreePath(((DefaultMutableTreeNode)node).getPath());
2136 else {
2137 ArrayList nodes = new ArrayList();
2138 TreeNode eachParent = node;
2139 while (eachParent != null) {
2140 nodes.add(eachParent);
2141 eachParent = eachParent.getParent();
2144 return new TreePath(ArrayUtil.toObjectArray(nodes));
2149 private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
2150 doWithUpdaterState(new Runnable() {
2151 public void run() {
2152 if (willAdjustSelection) {
2153 final TreePath path = getPathFor(node);
2154 if (myTree.isPathSelected(path)) {
2155 myTree.removeSelectionPath(path);
2159 myTreeModel.removeNodeFromParent(node);
2164 private void expandPath(final TreePath path, final boolean canSmartExpand) {
2165 doWithUpdaterState(new Runnable() {
2166 public void run() {
2167 if (path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2168 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2169 if (node.getChildCount() > 0 && !myTree.isExpanded(path)) {
2170 if (!canSmartExpand) {
2171 myNotForSmartExpand.add(node);
2173 try {
2174 myRequestedExpand = path;
2175 myTree.expandPath(path);
2176 processSmartExpand(node, canSmartExpand);
2178 finally {
2179 myNotForSmartExpand.remove(node);
2180 myRequestedExpand = null;
2183 else {
2184 processNodeActionsIfReady(node);
2191 private void doWithUpdaterState(Runnable runnable) {
2192 if (myUpdaterState != null) {
2193 myUpdaterState.process(runnable);
2195 else {
2196 runnable.run();
2200 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
2201 return descriptor.update();
2204 private void makeLoadingOrLeafIfNoChildren(final DefaultMutableTreeNode node) {
2205 TreePath path = getPathFor(node);
2206 if (path == null) return;
2208 insertLoadingNode(node, true);
2210 final NodeDescriptor descriptor = getDescriptorFrom(node);
2211 if (descriptor == null) return;
2213 descriptor.setChildrenSortingStamp(-1);
2215 if (getBuilder().isAlwaysShowPlus(descriptor)) return;
2218 TreePath parentPath = path.getParentPath();
2219 if (myTree.isVisible(path) || (parentPath != null && myTree.isExpanded(parentPath))) {
2220 if (myTree.isExpanded(path)) {
2221 addSubtreeToUpdate(node);
2223 else {
2224 insertLoadingNode(node, false);
2230 private boolean isValid(DefaultMutableTreeNode node) {
2231 if (node == null) return false;
2232 final Object object = node.getUserObject();
2233 if (object instanceof NodeDescriptor) {
2234 return isValid((NodeDescriptor)object);
2237 return false;
2240 private boolean isValid(NodeDescriptor descriptor) {
2241 if (descriptor == null) return false;
2242 return isValid(getElementFromDescriptor(descriptor));
2245 private boolean isValid(Object element) {
2246 if (element instanceof ValidateableNode) {
2247 if (!((ValidateableNode)element).isValid()) return false;
2249 return getBuilder().validateNode(element);
2252 private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
2253 if (!isLoadingChildrenFor(node)) {
2254 myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
2257 if (addToUnbuilt) {
2258 addToUnbuilt(node);
2263 protected void queueToBackground(@NotNull final Runnable bgBuildAction,
2264 @Nullable final Runnable edtPostRunnable,
2265 @Nullable final Runnable finalizeEdtRunnable) {
2266 registerWorkerTask(bgBuildAction);
2268 final Runnable pooledThreadWithProgressRunnable = new Runnable() {
2269 public void run() {
2270 if (isReleased()) {
2271 return;
2274 final AbstractTreeBuilder builder = getBuilder();
2276 builder.runBackgroundLoading(new Runnable() {
2277 public void run() {
2278 assertNotDispatchThread();
2280 if (isReleased()) {
2281 return;
2284 try {
2285 bgBuildAction.run();
2287 if (edtPostRunnable != null && !isReleased()) {
2288 builder.updateAfterLoadedInBackground(new Runnable() {
2289 public void run() {
2290 try {
2291 assertIsDispatchThread();
2293 if (isReleased()) {
2294 return;
2297 edtPostRunnable.run();
2299 finally {
2300 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2305 else {
2306 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2309 catch (ProcessCanceledException e) {
2310 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2312 catch (Throwable t) {
2313 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2314 throw new RuntimeException(t);
2321 Runnable pooledThreadRunnable = new Runnable() {
2322 public void run() {
2323 if (isReleased()) return;
2325 try {
2326 if (myProgress != null) {
2327 ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress);
2329 else {
2330 pooledThreadWithProgressRunnable.run();
2333 catch (ProcessCanceledException e) {
2334 //ignore
2339 if (isPassthroughMode()) {
2341 } else {
2342 if (myWorker == null || myWorker.isDisposed()) {
2343 myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
2344 myWorker.start();
2345 myWorker.addTaskFirst(pooledThreadRunnable);
2346 myWorker.dispose(false);
2348 else {
2349 myWorker.addTaskFirst(pooledThreadRunnable);
2354 private void registerWorkerTask(Runnable runnable) {
2355 synchronized (myActiveWorkerTasks) {
2356 myActiveWorkerTasks.add(runnable);
2360 private void unregisterWorkerTask(Runnable runnable, @Nullable Runnable finalizeRunnable) {
2361 boolean wasRemoved;
2362 synchronized (myActiveWorkerTasks) {
2363 wasRemoved = myActiveWorkerTasks.remove(runnable);
2366 if (wasRemoved && finalizeRunnable != null) {
2367 finalizeRunnable.run();
2370 maybeReady();
2373 public boolean isWorkerBusy() {
2374 synchronized (myActiveWorkerTasks) {
2375 return myActiveWorkerTasks.size() > 0;
2379 private void clearWorkerTasks() {
2380 synchronized (myActiveWorkerTasks) {
2381 myActiveWorkerTasks.clear();
2385 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node, boolean updatePosition) {
2386 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2387 NodeDescriptor descriptor = getDescriptorFrom(node);
2388 if (getElementFromDescriptor(descriptor) == null) return;
2390 boolean notified = false;
2391 if (updatePosition) {
2392 DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
2393 if (parentNode != null) {
2394 int oldIndex = parentNode.getIndex(node);
2395 int newIndex = oldIndex;
2396 if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
2397 final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
2398 for (int i = 0; i < parentNode.getChildCount(); i++) {
2399 children.add(parentNode.getChildAt(i));
2401 sortChildren(node, children, true, false);
2402 newIndex = children.indexOf(node);
2405 if (oldIndex != newIndex) {
2406 List<Object> pathsToExpand = new ArrayList<Object>();
2407 List<Object> selectionPaths = new ArrayList<Object>();
2408 TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
2409 removeNodeFromParent(node, false);
2410 myTreeModel.insertNodeInto(node, parentNode, newIndex);
2411 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
2412 notified = true;
2414 else {
2415 myTreeModel.nodeChanged(node);
2416 notified = true;
2419 else {
2420 myTreeModel.nodeChanged(node);
2421 notified = true;
2425 if (!notified) {
2426 myTreeModel.nodeChanged(node);
2431 public DefaultTreeModel getTreeModel() {
2432 return myTreeModel;
2435 private void insertNodesInto(final ArrayList<TreeNode> toInsert, final DefaultMutableTreeNode parentNode) {
2436 sortChildren(parentNode, toInsert, false, true);
2437 final ArrayList<TreeNode> all = new ArrayList<TreeNode>(toInsert.size() + parentNode.getChildCount());
2438 all.addAll(toInsert);
2439 all.addAll(TreeUtil.childrenToArray(parentNode));
2441 if (toInsert.size() > 0) {
2442 sortChildren(parentNode, all, true, true);
2444 int[] newNodeIndices = new int[toInsert.size()];
2445 int eachNewNodeIndex = 0;
2446 TreeMap<Integer, TreeNode> insertSet = new TreeMap<Integer, TreeNode>();
2447 for (int i = 0; i < toInsert.size(); i++) {
2448 TreeNode eachNewNode = toInsert.get(i);
2449 while (all.get(eachNewNodeIndex) != eachNewNode) {
2450 eachNewNodeIndex++;
2452 newNodeIndices[i] = eachNewNodeIndex;
2453 insertSet.put(eachNewNodeIndex, eachNewNode);
2456 Iterator<Integer> indices = insertSet.keySet().iterator();
2457 while (indices.hasNext()) {
2458 Integer eachIndex = indices.next();
2459 TreeNode eachNode = insertSet.get(eachIndex);
2460 parentNode.insert((MutableTreeNode)eachNode, eachIndex);
2463 myTreeModel.nodesWereInserted(parentNode, newNodeIndices);
2465 else {
2466 ArrayList<TreeNode> before = new ArrayList<TreeNode>();
2467 before.addAll(all);
2469 sortChildren(parentNode, all, true, false);
2470 if (!before.equals(all)) {
2471 doWithUpdaterState(new Runnable() {
2472 public void run() {
2473 parentNode.removeAllChildren();
2474 for (TreeNode each : all) {
2475 parentNode.add((MutableTreeNode)each);
2477 myTreeModel.nodeStructureChanged(parentNode);
2484 private void sortChildren(DefaultMutableTreeNode node, ArrayList<TreeNode> children, boolean updateStamp, boolean forceSort) {
2485 NodeDescriptor descriptor = getDescriptorFrom(node);
2486 assert descriptor != null;
2488 if (descriptor.getChildrenSortingStamp() >= getComparatorStamp() && !forceSort) return;
2489 if (children.size() > 0) {
2490 getBuilder().sortChildren(myNodeComparator, node, children);
2493 if (updateStamp) {
2494 descriptor.setChildrenSortingStamp(getComparatorStamp());
2498 private void disposeNode(DefaultMutableTreeNode node) {
2499 removeFromUpdating(node);
2500 removeFromUnbuilt(node);
2502 if (node.getChildCount() > 0) {
2503 for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
2504 disposeNode(_node);
2507 if (isLoadingNode(node)) return;
2508 NodeDescriptor descriptor = getDescriptorFrom(node);
2509 if (descriptor == null) return;
2510 final Object element = getElementFromDescriptor(descriptor);
2511 removeMapping(element, node, null);
2512 node.setUserObject(null);
2513 node.removeAllChildren();
2516 public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root) {
2517 return addSubtreeToUpdate(root, null);
2520 public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
2521 Object element = getElementFor(root);
2522 if (getTreeStructure().isAlwaysLeaf(element)) {
2523 removeLoading(root, true);
2525 if (runAfterUpdate != null) {
2526 getReady(this).doWhenDone(runAfterUpdate);
2528 return false;
2531 getUpdater().runAfterUpdate(runAfterUpdate);
2532 getUpdater().addSubtreeToUpdate(root);
2534 return true;
2537 public boolean wasRootNodeInitialized() {
2538 return myRootNodeWasInitialized;
2541 private boolean isRootNodeBuilt() {
2542 return myRootNodeWasInitialized && isNodeBeingBuilt(myRootNode);
2545 public void select(final Object[] elements, @Nullable final Runnable onDone) {
2546 select(elements, onDone, false);
2549 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
2550 select(elements, onDone, addToSelection, false);
2553 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) {
2554 _select(elements, onDone, addToSelection, true, false, true, deferred, false, false);
2557 void _select(final Object[] elements,
2558 final Runnable onDone,
2559 final boolean addToSelection,
2560 final boolean checkCurrentSelection,
2561 final boolean checkIfInStructure) {
2563 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, true, false, false, false);
2566 void _select(final Object[] elements,
2567 final Runnable onDone,
2568 final boolean addToSelection,
2569 final boolean checkCurrentSelection,
2570 final boolean checkIfInStructure,
2571 final boolean scrollToVisible) {
2573 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, false, false, false);
2576 public void userSelect(final Object[] elements,
2577 final Runnable onDone,
2578 final boolean addToSelection,
2579 boolean scroll) {
2580 _select(elements, onDone, addToSelection, true, false, scroll, false, true, true);
2583 void _select(final Object[] elements,
2584 final Runnable onDone,
2585 final boolean addToSelection,
2586 final boolean checkCurrentSelection,
2587 final boolean checkIfInStructure,
2588 final boolean scrollToVisible,
2589 final boolean deferred,
2590 final boolean canSmartExpand,
2591 final boolean mayQueue) {
2593 AbstractTreeUpdater updater = getUpdater();
2594 if (mayQueue && updater != null) {
2595 updater.queueSelection(new SelectionRequest(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, deferred, canSmartExpand));
2596 return;
2599 boolean willAffectSelection = elements.length > 0 || (elements.length == 0 && addToSelection);
2600 if (!willAffectSelection) {
2601 runDone(onDone);
2602 return;
2605 final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections;
2607 if (!deferred && wasRootNodeInitialized() && willAffectSelection) {
2608 myCanProcessDeferredSelections = false;
2611 if (!checkDeferred(deferred, onDone)) return;
2613 if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) {
2614 getTree().clearSelection();
2618 runDone(new Runnable() {
2619 public void run() {
2620 if (!checkDeferred(deferred, onDone)) return;
2622 final Set<Object> currentElements = getSelectedElements();
2624 if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
2625 boolean runSelection = false;
2626 for (Object eachToSelect : elements) {
2627 if (!currentElements.contains(eachToSelect)) {
2628 runSelection = true;
2629 break;
2633 if (!runSelection) {
2634 if (elements.length > 0) {
2635 selectVisible(elements[0], onDone, true, true, scrollToVisible);
2637 return;
2641 Set<Object> toSelect = new HashSet<Object>();
2642 myTree.clearSelection();
2643 toSelect.addAll(Arrays.asList(elements));
2644 if (addToSelection) {
2645 toSelect.addAll(currentElements);
2648 if (checkIfInStructure) {
2649 final Iterator<Object> allToSelect = toSelect.iterator();
2650 while (allToSelect.hasNext()) {
2651 Object each = allToSelect.next();
2652 if (!isInStructure(each)) {
2653 allToSelect.remove();
2658 final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
2660 if (wasRootNodeInitialized()) {
2661 final int[] originalRows = myTree.getSelectionRows();
2662 if (!addToSelection) {
2663 myTree.clearSelection();
2665 addNext(elementsToSelect, 0, new Runnable() {
2666 public void run() {
2667 if (getTree().isSelectionEmpty()) {
2668 restoreSelection(currentElements);
2670 runDone(onDone);
2672 }, originalRows, deferred, scrollToVisible, canSmartExpand);
2674 else {
2675 addToDeferred(elementsToSelect, onDone);
2681 private void restoreSelection(Set<Object> selection) {
2682 for (Object each : selection) {
2683 DefaultMutableTreeNode node = getNodeForElement(each, false);
2684 if (node != null && isValidForSelectionAdjusting(node)) {
2685 addSelectionPath(getPathFor(node), false, null);
2691 private void addToDeferred(final Object[] elementsToSelect, final Runnable onDone) {
2692 myDeferredSelections.clear();
2693 myDeferredSelections.add(new Runnable() {
2694 public void run() {
2695 select(elementsToSelect, onDone, false, true);
2700 private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) {
2701 if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) {
2702 return true;
2704 else {
2705 runDone(onDone);
2706 return false;
2710 @NotNull
2711 final Set<Object> getSelectedElements() {
2712 final TreePath[] paths = myTree.getSelectionPaths();
2714 Set<Object> result = new HashSet<Object>();
2715 if (paths != null) {
2716 for (TreePath eachPath : paths) {
2717 if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2718 final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
2719 final Object eachElement = getElementFor(eachNode);
2720 if (eachElement != null) {
2721 result.add(eachElement);
2726 return result;
2730 private void addNext(final Object[] elements,
2731 final int i,
2732 @Nullable final Runnable onDone,
2733 final int[] originalRows,
2734 final boolean deferred,
2735 final boolean scrollToVisible,
2736 final boolean canSmartExpand) {
2737 if (i >= elements.length) {
2738 if (myTree.isSelectionEmpty()) {
2739 myTree.setSelectionRows(originalRows);
2741 runDone(onDone);
2743 else {
2744 if (!checkDeferred(deferred, onDone)) {
2745 return;
2748 doSelect(elements[i], new Runnable() {
2749 public void run() {
2750 if (!checkDeferred(deferred, onDone)) return;
2752 addNext(elements, i + 1, onDone, originalRows, deferred, scrollToVisible, canSmartExpand);
2754 }, true, deferred, i == 0, scrollToVisible, canSmartExpand);
2758 public void select(final Object element, @Nullable final Runnable onDone) {
2759 select(element, onDone, false);
2762 public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
2763 _select(new Object[]{element}, onDone, addToSelection, true, false);
2766 private void doSelect(final Object element,
2767 final Runnable onDone,
2768 final boolean addToSelection,
2769 final boolean deferred,
2770 final boolean canBeCentered,
2771 final boolean scrollToVisible,
2772 boolean canSmartExpand) {
2773 final Runnable _onDone = new Runnable() {
2774 public void run() {
2775 if (!checkDeferred(deferred, onDone)) return;
2776 selectVisible(element, onDone, addToSelection, canBeCentered, scrollToVisible);
2779 _expand(element, _onDone, true, false, canSmartExpand);
2782 public void scrollSelectionToVisible(@Nullable Runnable onDone, boolean shouldBeCentered) {
2783 int[] rows = myTree.getSelectionRows();
2784 if (rows == null || rows.length == 0) {
2785 runDone(onDone);
2786 return;
2790 Object toSelect = null;
2791 for (int eachRow : rows) {
2792 TreePath path = myTree.getPathForRow(eachRow);
2793 toSelect = getElementFor(path.getLastPathComponent());
2794 if (toSelect != null) break;
2797 if (toSelect != null) {
2798 selectVisible(toSelect, onDone, true, shouldBeCentered, true);
2802 private void selectVisible(Object element, final Runnable onDone, boolean addToSelection, boolean canBeCentered, final boolean scroll) {
2803 final DefaultMutableTreeNode toSelect = getNodeForElement(element, false);
2805 if (toSelect == null) {
2806 runDone(onDone);
2807 return;
2810 if (getRootNode() == toSelect && !myTree.isRootVisible()) {
2811 runDone(onDone);
2812 return;
2815 final int row = myTree.getRowForPath(new TreePath(toSelect.getPath()));
2817 if (myUpdaterState != null) {
2818 myUpdaterState.addSelection(element);
2821 if (Registry.is("ide.tree.autoscrollToVCenter") && canBeCentered) {
2822 runDone(new Runnable() {
2823 public void run() {
2824 TreeUtil.showRowCentered(myTree, row, false, scroll).doWhenDone(new Runnable() {
2825 public void run() {
2826 runDone(onDone);
2832 else {
2833 TreeUtil.showAndSelect(myTree, row - 2, row + 2, row, -1, addToSelection, scroll).doWhenDone(new Runnable() {
2834 public void run() {
2835 runDone(onDone);
2841 public void expand(final Object element, @Nullable final Runnable onDone) {
2842 expand(new Object[]{element}, onDone);
2845 public void expand(final Object[] element, @Nullable final Runnable onDone) {
2846 expand(element, onDone, false);
2850 void expand(final Object element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
2851 _expand(new Object[]{element}, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure, false);
2854 void expand(final Object[] element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
2855 _expand(element, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure, false);
2858 void _expand(final Object[] element,
2859 @NotNull final Runnable onDone,
2860 final boolean parentsOnly,
2861 final boolean checkIfInStructure,
2862 final boolean canSmartExpand) {
2864 runDone(new Runnable() {
2865 public void run() {
2866 if (element.length == 0) {
2867 runDone(onDone);
2868 return;
2871 if (myUpdaterState != null) {
2872 myUpdaterState.clearExpansion();
2876 final ActionCallback done = new ActionCallback(element.length);
2877 done.doWhenDone(new Runnable() {
2878 public void run() {
2879 runDone(onDone);
2883 expandNext(element, 0, parentsOnly, checkIfInStructure, canSmartExpand, done);
2888 private void expandNext(final Object[] elements, final int index, final boolean parentsOnly, final boolean checkIfInStricture, final boolean canSmartExpand, final ActionCallback done) {
2889 if (elements.length <= 0) {
2890 done.setDone();
2891 return;
2894 if (index >= elements.length) {
2895 return;
2898 _expand(elements[index], new Runnable() {
2899 public void run() {
2900 done.setDone();
2901 expandNext(elements, index + 1, parentsOnly, checkIfInStricture, canSmartExpand, done);
2903 }, parentsOnly, checkIfInStricture, canSmartExpand);
2906 public void collapseChildren(final Object element, @Nullable final Runnable onDone) {
2907 runDone(new Runnable() {
2908 public void run() {
2909 final DefaultMutableTreeNode node = getNodeForElement(element, false);
2910 if (node != null) {
2911 getTree().collapsePath(new TreePath(node.getPath()));
2912 runDone(onDone);
2918 private void runDone(@Nullable Runnable done) {
2919 if (isReleased()) return;
2920 if (done == null) return;
2922 if (isYeildingNow()) {
2923 if (!myYeildingDoneRunnables.contains(done)) {
2924 myYeildingDoneRunnables.add(done);
2927 else {
2928 done.run();
2932 private void _expand(final Object element,
2933 @NotNull final Runnable onDone,
2934 final boolean parentsOnly,
2935 boolean checkIfInStructure,
2936 boolean canSmartExpand) {
2938 if (checkIfInStructure && !isInStructure(element)) {
2939 runDone(onDone);
2940 return;
2943 if (wasRootNodeInitialized()) {
2944 List<Object> kidsToExpand = new ArrayList<Object>();
2945 Object eachElement = element;
2946 DefaultMutableTreeNode firstVisible = null;
2947 while (true) {
2948 if (!isValid(eachElement)) break;
2950 firstVisible = getNodeForElement(eachElement, true);
2951 if (eachElement != element || !parentsOnly) {
2952 assert !kidsToExpand.contains(eachElement) :
2953 "Not a valid tree structure, walking up the structure gives many entries for element=" +
2954 eachElement +
2955 ", root=" +
2956 getTreeStructure().getRootElement();
2957 kidsToExpand.add(eachElement);
2959 if (firstVisible != null) break;
2960 eachElement = getTreeStructure().getParentElement(eachElement);
2961 if (eachElement == null) {
2962 firstVisible = null;
2963 break;
2968 if (firstVisible == null) {
2969 runDone(onDone);
2971 else if (kidsToExpand.size() == 0) {
2972 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)firstVisible.getParent();
2973 if (parentNode != null) {
2974 final TreePath parentPath = new TreePath(parentNode.getPath());
2975 if (!myTree.isExpanded(parentPath)) {
2976 expand(parentPath, canSmartExpand);
2979 runDone(onDone);
2981 else {
2982 processExpand(firstVisible, kidsToExpand, kidsToExpand.size() - 1, onDone, canSmartExpand);
2985 else {
2986 deferExpansion(element, onDone, parentsOnly, canSmartExpand);
2990 private void deferExpansion(final Object element, final Runnable onDone, final boolean parentsOnly, final boolean canSmartExpand) {
2991 myDeferredExpansions.add(new Runnable() {
2992 public void run() {
2993 _expand(element, onDone, parentsOnly, false, canSmartExpand);
2998 private void processExpand(final DefaultMutableTreeNode toExpand,
2999 final List kidsToExpand,
3000 final int expandIndex,
3001 @NotNull final Runnable onDone,
3002 final boolean canSmartExpand) {
3004 final Object element = getElementFor(toExpand);
3005 if (element == null) {
3006 runDone(onDone);
3007 return;
3010 addNodeAction(element, new NodeAction() {
3011 public void onReady(final DefaultMutableTreeNode node) {
3013 if (node.getChildCount() > 0 && !myTree.isExpanded(new TreePath(node.getPath()))) {
3014 if (!isAutoExpand(node)) {
3015 expand(node, canSmartExpand);
3019 if (expandIndex <= 0) {
3020 runDone(onDone);
3021 return;
3024 final DefaultMutableTreeNode nextNode = getNodeForElement(kidsToExpand.get(expandIndex - 1), false);
3025 if (nextNode != null) {
3026 processExpand(nextNode, kidsToExpand, expandIndex - 1, onDone, canSmartExpand);
3028 else {
3029 runDone(onDone);
3032 }, true);
3035 if (myTree.isExpanded(getPathFor(toExpand)) && !myUnbuiltNodes.contains(toExpand)) {
3036 if (!areChildrenToBeUpdated(toExpand)) {
3037 processNodeActionsIfReady(toExpand);
3040 else {
3041 if (!myUnbuiltNodes.contains(toExpand)) {
3042 addSubtreeToUpdate(toExpand);
3044 else {
3045 expand(toExpand, canSmartExpand);
3050 private boolean areChildrenToBeUpdated(DefaultMutableTreeNode node) {
3051 return getUpdater().isEnqueuedToUpdate(node) || isUpdatingParent(node);
3054 private String asString(DefaultMutableTreeNode node) {
3055 if (node == null) return null;
3057 StringBuffer children = new StringBuffer(node.toString());
3058 children.append(" [");
3059 for (int i = 0; i < node.getChildCount(); i++) {
3060 children.append(node.getChildAt(i));
3061 if (i < node.getChildCount() - 1) {
3062 children.append(",");
3065 children.append("]");
3067 return children.toString();
3070 @Nullable
3071 public Object getElementFor(Object node) {
3072 if (!(node instanceof DefaultMutableTreeNode)) return null;
3073 return getElementFor((DefaultMutableTreeNode)node);
3076 @Nullable
3077 Object getElementFor(DefaultMutableTreeNode node) {
3078 if (node != null) {
3079 final Object o = node.getUserObject();
3080 if (o instanceof NodeDescriptor) {
3081 return getElementFromDescriptor(((NodeDescriptor)o));
3085 return null;
3088 public final boolean isNodeBeingBuilt(final TreePath path) {
3089 return isNodeBeingBuilt(path.getLastPathComponent());
3092 public final boolean isNodeBeingBuilt(Object node) {
3093 if (isParentLoading(node) || isLoadingParent(node)) return true;
3095 final boolean childrenAreNoLoadedYet = myUnbuiltNodes.contains(node);
3096 if (childrenAreNoLoadedYet) {
3097 if (node instanceof DefaultMutableTreeNode) {
3098 final TreePath nodePath = new TreePath(((DefaultMutableTreeNode)node).getPath());
3099 if (!myTree.isExpanded(nodePath)) return false;
3102 return true;
3106 return false;
3109 private boolean isLoadingParent(Object node) {
3110 if (!(node instanceof DefaultMutableTreeNode)) return false;
3111 return isLoadedInBackground(getElementFor((DefaultMutableTreeNode)node));
3114 public void setTreeStructure(final AbstractTreeStructure treeStructure) {
3115 myTreeStructure = treeStructure;
3116 clearUpdaterState();
3119 public AbstractTreeUpdater getUpdater() {
3120 return myUpdater;
3123 public void setUpdater(final AbstractTreeUpdater updater) {
3124 myUpdater = updater;
3125 if (updater != null && myUpdateIfInactive) {
3126 updater.showNotify();
3129 if (myUpdater != null) {
3130 myUpdater.setPassThroughMode(myPassthroughMode);
3134 public DefaultMutableTreeNode getRootNode() {
3135 return myRootNode;
3138 public void setRootNode(@NotNull final DefaultMutableTreeNode rootNode) {
3139 myRootNode = rootNode;
3142 private void dropUpdaterStateIfExternalChange() {
3143 if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) {
3144 clearUpdaterState();
3148 void clearUpdaterState() {
3149 myUpdaterState = null;
3152 private void createMapping(Object element, DefaultMutableTreeNode node) {
3153 if (!myElementToNodeMap.containsKey(element)) {
3154 myElementToNodeMap.put(element, node);
3156 else {
3157 final Object value = myElementToNodeMap.get(element);
3158 final List<DefaultMutableTreeNode> nodes;
3159 if (value instanceof DefaultMutableTreeNode) {
3160 nodes = new ArrayList<DefaultMutableTreeNode>();
3161 nodes.add((DefaultMutableTreeNode)value);
3162 myElementToNodeMap.put(element, nodes);
3164 else {
3165 nodes = (List<DefaultMutableTreeNode>)value;
3167 nodes.add(node);
3171 private void removeMapping(Object element, DefaultMutableTreeNode node, @Nullable Object elementToPutNodeActionsFor) {
3172 final Object value = myElementToNodeMap.get(element);
3173 if (value != null) {
3174 if (value instanceof DefaultMutableTreeNode) {
3175 if (value.equals(node)) {
3176 myElementToNodeMap.remove(element);
3179 else {
3180 List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
3181 final boolean reallyRemoved = nodes.remove(node);
3182 if (reallyRemoved) {
3183 if (nodes.isEmpty()) {
3184 myElementToNodeMap.remove(element);
3190 remapNodeActions(element, elementToPutNodeActionsFor);
3193 private void remapNodeActions(Object element, Object elementToPutNodeActionsFor) {
3194 _remapNodeActions(element, elementToPutNodeActionsFor, myNodeActions);
3195 _remapNodeActions(element, elementToPutNodeActionsFor, myNodeChildrenActions);
3198 private void _remapNodeActions(Object element, Object elementToPutNodeActionsFor, final Map<Object, List<NodeAction>> nodeActions) {
3199 final List<NodeAction> actions = nodeActions.get(element);
3200 nodeActions.remove(element);
3202 if (elementToPutNodeActionsFor != null && actions != null) {
3203 nodeActions.put(elementToPutNodeActionsFor, actions);
3207 private DefaultMutableTreeNode getFirstNode(Object element) {
3208 return findNode(element, 0);
3211 private DefaultMutableTreeNode findNode(final Object element, int startIndex) {
3212 final Object value = getBuilder().findNodeByElement(element);
3213 if (value == null) {
3214 return null;
3216 if (value instanceof DefaultMutableTreeNode) {
3217 return startIndex == 0 ? (DefaultMutableTreeNode)value : null;
3219 final List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
3220 return startIndex < nodes.size() ? nodes.get(startIndex) : null;
3223 protected Object findNodeByElement(Object element) {
3224 if (myElementToNodeMap.containsKey(element)) {
3225 return myElementToNodeMap.get(element);
3228 try {
3229 TREE_NODE_WRAPPER.setValue(element);
3230 return myElementToNodeMap.get(TREE_NODE_WRAPPER);
3232 finally {
3233 TREE_NODE_WRAPPER.setValue(null);
3237 private DefaultMutableTreeNode findNodeForChildElement(DefaultMutableTreeNode parentNode, Object element) {
3238 final Object value = myElementToNodeMap.get(element);
3239 if (value == null) {
3240 return null;
3243 if (value instanceof DefaultMutableTreeNode) {
3244 final DefaultMutableTreeNode elementNode = (DefaultMutableTreeNode)value;
3245 return parentNode.equals(elementNode.getParent()) ? elementNode : null;
3248 final List<DefaultMutableTreeNode> allNodesForElement = (List<DefaultMutableTreeNode>)value;
3249 for (final DefaultMutableTreeNode elementNode : allNodesForElement) {
3250 if (parentNode.equals(elementNode.getParent())) {
3251 return elementNode;
3255 return null;
3258 public void cancelBackgroundLoading() {
3259 if (myWorker != null) {
3260 myWorker.cancelTasks();
3261 clearWorkerTasks();
3264 clearNodeActions();
3267 private void addNodeAction(Object element, NodeAction action, boolean shouldChildrenBeReady) {
3268 _addNodeAction(element, action, myNodeActions);
3269 if (shouldChildrenBeReady) {
3270 _addNodeAction(element, action, myNodeChildrenActions);
3275 private void _addNodeAction(Object element, NodeAction action, Map<Object, List<NodeAction>> map) {
3276 maybeSetBusyAndScheduleWaiterForReady(true);
3277 List<NodeAction> list = map.get(element);
3278 if (list == null) {
3279 list = new ArrayList<NodeAction>();
3280 map.put(element, list);
3282 list.add(action);
3286 private void cleanUpNow() {
3287 if (isReleased()) return;
3289 final UpdaterTreeState state = new UpdaterTreeState(this);
3291 myTree.collapsePath(new TreePath(myTree.getModel().getRoot()));
3292 myTree.clearSelection();
3293 getRootNode().removeAllChildren();
3295 myRootNodeWasInitialized = false;
3296 clearNodeActions();
3297 myElementToNodeMap.clear();
3298 myDeferredSelections.clear();
3299 myDeferredExpansions.clear();
3300 myLoadedInBackground.clear();
3301 myUnbuiltNodes.clear();
3302 myUpdateFromRootRequested = true;
3304 if (myWorker != null) {
3305 Disposer.dispose(myWorker);
3306 myWorker = null;
3309 myTree.invalidate();
3311 state.restore(null);
3314 public AbstractTreeUi setClearOnHideDelay(final long clearOnHideDelay) {
3315 myClearOnHideDelay = clearOnHideDelay;
3316 return this;
3319 public void setJantorPollPeriod(final long time) {
3320 myJanitorPollPeriod = time;
3323 public void setCheckStructure(final boolean checkStructure) {
3324 myCheckStructure = checkStructure;
3327 private class MySelectionListener implements TreeSelectionListener {
3328 public void valueChanged(final TreeSelectionEvent e) {
3329 dropUpdaterStateIfExternalChange();
3334 private class MyExpansionListener implements TreeExpansionListener {
3335 public void treeExpanded(TreeExpansionEvent event) {
3336 dropUpdaterStateIfExternalChange();
3338 TreePath path = event.getPath();
3340 if (myRequestedExpand != null && !myRequestedExpand.equals(path)) return;
3342 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
3344 if (!myUnbuiltNodes.contains(node)) {
3345 removeLoading(node, false);
3347 boolean hasUnbuiltChildren = false;
3348 for (int i = 0; i < node.getChildCount(); i++) {
3349 DefaultMutableTreeNode each = (DefaultMutableTreeNode)node.getChildAt(i);
3350 if (myUnbuiltNodes.contains(each)) {
3351 makeLoadingOrLeafIfNoChildren(each);
3352 hasUnbuiltChildren = true;
3356 if (hasUnbuiltChildren) {
3357 addSubtreeToUpdate(node);
3360 else {
3361 getBuilder().expandNodeChildren(node);
3364 processSmartExpand(node, canSmartExpand(node, true));
3365 processNodeActionsIfReady(node);
3368 public void treeCollapsed(TreeExpansionEvent e) {
3369 dropUpdaterStateIfExternalChange();
3371 final TreePath path = e.getPath();
3372 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
3373 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
3376 TreePath pathToSelect = null;
3377 if (isSelectionInside(node)) {
3378 pathToSelect = new TreePath(node.getPath());
3382 NodeDescriptor descriptor = getDescriptorFrom(node);
3383 if (getBuilder().isDisposeOnCollapsing(descriptor)) {
3384 runDone(new Runnable() {
3385 public void run() {
3386 if (isDisposed(node)) return;
3388 TreePath nodePath = new TreePath(node.getPath());
3389 if (myTree.isExpanded(nodePath)) return;
3391 removeChildren(node);
3392 makeLoadingOrLeafIfNoChildren(node);
3395 if (node.equals(getRootNode())) {
3396 if (myTree.isRootVisible()) {
3397 //todo kirillk to investigate -- should be done by standard selction move
3398 //addSelectionPath(new TreePath(getRootNode().getPath()), true, Condition.FALSE);
3401 else {
3402 myTreeModel.reload(node);
3406 if (pathToSelect != null && myTree.isSelectionEmpty()) {
3407 addSelectionPath(pathToSelect, true, Condition.FALSE);
3411 private void removeChildren(DefaultMutableTreeNode node) {
3412 EnumerationCopy copy = new EnumerationCopy(node.children());
3413 while (copy.hasMoreElements()) {
3414 disposeNode((DefaultMutableTreeNode)copy.nextElement());
3416 node.removeAllChildren();
3417 myTreeModel.nodeStructureChanged(node);
3420 private boolean isSelectionInside(DefaultMutableTreeNode parent) {
3421 TreePath path = new TreePath(myTreeModel.getPathToRoot(parent));
3422 TreePath[] paths = myTree.getSelectionPaths();
3423 if (paths == null) return false;
3424 for (TreePath path1 : paths) {
3425 if (path.isDescendant(path1)) return true;
3427 return false;
3431 public boolean isInStructure(@Nullable Object element) {
3432 Object eachParent = element;
3433 while (eachParent != null) {
3434 if (getTreeStructure().getRootElement().equals(eachParent)) return true;
3435 eachParent = getTreeStructure().getParentElement(eachParent);
3438 return false;
3441 interface NodeAction {
3442 void onReady(DefaultMutableTreeNode node);
3445 public void setCanYield(final boolean canYield) {
3446 myCanYield = canYield;
3449 public Collection<TreeUpdatePass> getYeildingPasses() {
3450 return myYeildingPasses;
3453 public boolean isBuilt(Object element) {
3454 if (!myElementToNodeMap.containsKey(element)) return false;
3455 final Object node = myElementToNodeMap.get(element);
3456 return !myUnbuiltNodes.contains(node);
3459 static class LoadedChildren {
3461 private List myElements;
3462 private Map<Object, NodeDescriptor> myDescriptors = new HashMap<Object, NodeDescriptor>();
3463 private Map<NodeDescriptor, Boolean> myChanges = new HashMap<NodeDescriptor, Boolean>();
3465 LoadedChildren(Object[] elements) {
3466 myElements = Arrays.asList(elements != null ? elements : new Object[0]);
3469 void putDescriptor(Object element, NodeDescriptor descriptor, boolean isChanged) {
3470 assert myElements.contains(element);
3471 myDescriptors.put(element, descriptor);
3472 myChanges.put(descriptor, isChanged);
3475 List getElements() {
3476 return myElements;
3479 NodeDescriptor getDescriptor(Object element) {
3480 return myDescriptors.get(element);
3483 @Override
3484 public String toString() {
3485 return Arrays.asList(myElements) + "->" + myChanges;
3488 public boolean isUpdated(Object element) {
3489 NodeDescriptor desc = getDescriptor(element);
3490 return myChanges.get(desc);
3494 UpdaterTreeState getUpdaterState() {
3495 return myUpdaterState;
3498 private ActionCallback addReadyCallback(Object requestor) {
3499 synchronized (myReadyCallbacks) {
3500 ActionCallback cb = myReadyCallbacks.get(requestor);
3501 if (cb == null) {
3502 cb = new ActionCallback();
3503 myReadyCallbacks.put(requestor, cb);
3506 return cb;
3510 private ActionCallback[] getReadyCallbacks(boolean clear) {
3511 synchronized (myReadyCallbacks) {
3512 ActionCallback[] result = myReadyCallbacks.values().toArray(new ActionCallback[myReadyCallbacks.size()]);
3513 if (clear) {
3514 myReadyCallbacks.clear();
3516 return result;
3520 private long getComparatorStamp() {
3521 if (myNodeDescriptorComparator instanceof NodeDescriptor.NodeComparator) {
3522 long currentComparatorStamp = ((NodeDescriptor.NodeComparator)myNodeDescriptorComparator).getStamp();
3523 if (currentComparatorStamp > myLastComparatorStamp) {
3524 myOwnComparatorStamp = Math.max(myOwnComparatorStamp, currentComparatorStamp) + 1;
3526 myLastComparatorStamp = currentComparatorStamp;
3528 return Math.max(currentComparatorStamp, myOwnComparatorStamp);
3530 else {
3531 return myOwnComparatorStamp;
3535 public void incComparatorStamp() {
3536 myOwnComparatorStamp = getComparatorStamp() + 1;
3539 public static class UpdateInfo {
3540 NodeDescriptor myDescriptor;
3541 TreeUpdatePass myPass;
3542 boolean myCanSmartExpand;
3543 boolean myWasExpanded;
3544 boolean myForceUpdate;
3545 boolean myDescriptorIsUpToDate;
3547 public UpdateInfo(NodeDescriptor descriptor,
3548 TreeUpdatePass pass,
3549 boolean canSmartExpand,
3550 boolean wasExpanded,
3551 boolean forceUpdate,
3552 boolean descriptorIsUpToDate) {
3553 myDescriptor = descriptor;
3554 myPass = pass;
3555 myCanSmartExpand = canSmartExpand;
3556 myWasExpanded = wasExpanded;
3557 myForceUpdate = forceUpdate;
3558 myDescriptorIsUpToDate = descriptorIsUpToDate;
3561 synchronized NodeDescriptor getDescriptor() {
3562 return myDescriptor;
3565 synchronized TreeUpdatePass getPass() {
3566 return myPass;
3569 synchronized boolean isCanSmartExpand() {
3570 return myCanSmartExpand;
3573 synchronized boolean isWasExpanded() {
3574 return myWasExpanded;
3577 synchronized boolean isForceUpdate() {
3578 return myForceUpdate;
3581 synchronized boolean isDescriptorIsUpToDate() {
3582 return myDescriptorIsUpToDate;
3585 public synchronized void apply(UpdateInfo updateInfo) {
3586 myDescriptor = updateInfo.myDescriptor;
3587 myPass = updateInfo.myPass;
3588 myCanSmartExpand = updateInfo.myCanSmartExpand;
3589 myWasExpanded = updateInfo.myWasExpanded;
3590 myForceUpdate = updateInfo.myForceUpdate;
3591 myDescriptorIsUpToDate = updateInfo.myDescriptorIsUpToDate;
3596 public void setPassthroughMode(boolean passthrough) {
3597 myPassthroughMode = passthrough;
3598 AbstractTreeUpdater updater = getUpdater();
3600 if (updater != null) {
3601 updater.setPassThroughMode(myPassthroughMode);
3604 if (!isUnitTestingMode() && passthrough) {
3605 LOG.error("Pass-through mode for TreeUi is allowed only for unit test mode");
3609 public boolean isPassthroughMode() {
3610 return myPassthroughMode;
3613 private boolean isUnitTestingMode() {
3614 Application app = ApplicationManager.getApplication();
3615 return app != null && app.isUnitTestMode();