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.ide.UiActivity;
20 import com.intellij.ide.UiActivityMonitor;
21 import com.intellij.openapi.application.Application;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.progress.*;
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.Time;
34 import com.intellij.util.concurrency.WorkerThread;
35 import com.intellij.util.containers.ContainerUtil;
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;
46 import javax.swing.event.*;
47 import javax.swing.tree.*;
49 import java.awt.event.FocusAdapter;
50 import java.awt.event.FocusEvent;
52 import java.util.List;
53 import java.util.concurrent.TimeUnit;
54 import java.util.concurrent.atomic.AtomicBoolean;
55 import java.util.concurrent.locks.Lock;
56 import java.util.concurrent.locks.ReentrantLock;
58 public class AbstractTreeUi {
59 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
60 protected JTree myTree;// protected for TestNG
61 @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel;
62 private AbstractTreeStructure myTreeStructure;
63 private AbstractTreeUpdater myUpdater;
65 private Comparator<NodeDescriptor> myNodeDescriptorComparator;
66 private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() {
67 public int compare(TreeNode n1, TreeNode n2) {
68 if (isLoadingNode(n1) || isLoadingNode(n2)) return 0;
69 //todo kirillk hard to undertstand why nodes may be null, just avoid NPEs
70 if (n1 == null || n2 == null) return 0;
72 NodeDescriptor nodeDescriptor1 = getDescriptorFrom((DefaultMutableTreeNode)n1);
73 NodeDescriptor nodeDescriptor2 = getDescriptorFrom((DefaultMutableTreeNode)n2);
75 if (nodeDescriptor1 == null || nodeDescriptor2 == null) return 0;
77 return myNodeDescriptorComparator != null
78 ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2)
79 : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex();
82 long myOwnComparatorStamp;
83 long myLastComparatorStamp;
85 private DefaultMutableTreeNode myRootNode;
86 private final HashMap<Object, Object> myElementToNodeMap = new HashMap<Object, Object>();
87 private final HashSet<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>();
88 private TreeExpansionListener myExpansionListener;
89 private MySelectionListener mySelectionListener;
91 private WorkerThread myWorker = null;
92 private final Set<Runnable> myActiveWorkerTasks = new HashSet<Runnable>();
94 private ProgressIndicator myProgress;
95 private static final int WAIT_CURSOR_DELAY = 100;
96 private AbstractTreeNode<Object> TREE_NODE_WRAPPER;
98 private boolean myRootNodeWasQueuedToInitialize = false;
99 private boolean myRootNodeInitialized = false;
101 private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>();
102 private boolean myUpdateFromRootRequested;
103 private boolean myWasEverShown;
104 private boolean myUpdateIfInactive;
106 private final Map<Object, UpdateInfo> myLoadedInBackground = new HashMap<Object, UpdateInfo>();
107 private final Map<Object, List<NodeAction>> myNodeChildrenActions = new HashMap<Object, List<NodeAction>>();
109 private long myClearOnHideDelay = -1;
110 private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
112 private final Set<Runnable> myDeferredSelections = new HashSet<Runnable>();
113 private final Set<Runnable> myDeferredExpansions = new HashSet<Runnable>();
115 private boolean myCanProcessDeferredSelections;
117 private UpdaterTreeState myUpdaterState;
118 private AbstractTreeBuilder myBuilder;
120 private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
121 private long myJanitorPollPeriod = Time.SECOND * 10;
123 private boolean myCanYield = false;
125 private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
127 private boolean myYeildingNow;
129 private final Set<DefaultMutableTreeNode> myPendingNodeActions = new HashSet<DefaultMutableTreeNode>();
130 private final Set<Runnable> myYeildingDoneRunnables = new HashSet<Runnable>();
132 private final Alarm myBusyAlarm = new Alarm();
133 private final Runnable myWaiterForReady = new Runnable() {
135 maybeSetBusyAndScheduleWaiterForReady(false, null);
139 private final RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
140 private final RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
141 private final RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimeout");
143 private boolean myWasEverIndexNotReady;
144 private boolean myShowing;
145 private final FocusAdapter myFocusListener = new FocusAdapter() {
147 public void focusGained(FocusEvent e) {
151 private final Set<DefaultMutableTreeNode> myNotForSmartExpand = new HashSet<DefaultMutableTreeNode>();
152 private TreePath myRequestedExpand;
154 private TreePath mySilentExpand;
155 private TreePath mySilentSelect;
157 private final ActionCallback myInitialized = new ActionCallback();
158 private final BusyObject.Impl myBusyObject = new BusyObject.Impl() {
160 public boolean isReady() {
161 return AbstractTreeUi.this.isReady(true);
165 protected void onReadyWasSent() {
170 private boolean myPassthroughMode = false;
173 private final Set<Object> myAutoExpandRoots = new HashSet<Object>();
174 private final RegistryValue myAutoExpandDepth = Registry.get("ide.tree.autoExpandMaxDepth");
176 private final Set<DefaultMutableTreeNode> myWillBeExpaned = new HashSet<DefaultMutableTreeNode>();
177 private SimpleTimerTask myCleanupTask;
179 private final AtomicBoolean myCancelRequest = new AtomicBoolean();
180 private final Lock myStateLock = new ReentrantLock();
181 private final AtomicBoolean myLockWasAcquired = new AtomicBoolean();
183 private final AtomicBoolean myResettingToReadyNow = new AtomicBoolean();
185 private final Map<Progressive, ProgressIndicator> myBatchIndicators = new HashMap<Progressive, ProgressIndicator>();
186 private final Map<Progressive, ActionCallback> myBatchCallbacks = new HashMap<Progressive, ActionCallback>();
188 private final Map<DefaultMutableTreeNode, DefaultMutableTreeNode> myCancelledBuild = new WeakHashMap<DefaultMutableTreeNode, DefaultMutableTreeNode>();
190 private boolean mySelectionIsAdjusted;
191 private boolean myReleaseRequested;
193 private boolean mySelectionIsBeingAdjusted;
195 private final Set<Object> myRevalidatedObjects = new HashSet<Object>();
197 private final Set<Runnable> myUserRunnables = new HashSet<Runnable>();
199 private final Alarm myMaybeReady = new Alarm();
200 private final Runnable myMaybeReadyRunnable = new Runnable() {
206 private UiActivityMonitor myActivityMonitor;
207 private UiActivity myActivityId;
209 protected void init(AbstractTreeBuilder builder,
211 DefaultTreeModel treeModel,
212 AbstractTreeStructure treeStructure,
213 @Nullable Comparator<NodeDescriptor> comparator,
214 boolean updateIfInactive) {
217 myTreeModel = treeModel;
218 myActivityMonitor = UiActivityMonitor.getInstance();
219 myActivityId = new UiActivity.AsyncBgOperation("TreeUi" + this);
220 addModelListenerToDianoseAccessOutsideEdt();
221 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
222 myTree.setModel(myTreeModel);
223 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
224 setTreeStructure(treeStructure);
225 myNodeDescriptorComparator = comparator;
226 myUpdateIfInactive = updateIfInactive;
228 UIUtil.invokeLaterIfNeeded(new Runnable() {
230 if (!wasRootNodeInitialized()) {
231 if (myRootNode.getChildCount() == 0) {
232 insertLoadingNode(myRootNode, true);
238 myExpansionListener = new MyExpansionListener();
239 myTree.addTreeExpansionListener(myExpansionListener);
241 mySelectionListener = new MySelectionListener();
242 myTree.addTreeSelectionListener(mySelectionListener);
244 setUpdater(getBuilder().createUpdater());
245 myProgress = getBuilder().createProgressIndicator();
246 Disposer.register(getBuilder(), getUpdater());
248 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
249 public void showNotify() {
251 myWasEverShown = true;
252 if (canInitiateNewActivity()) {
257 public void hideNotify() {
259 if (canInitiateNewActivity()) {
264 Disposer.register(getBuilder(), uiNotify);
266 myTree.addFocusListener(myFocusListener);
270 boolean isNodeActionsPending() {
271 return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty();
274 private void clearNodeActions() {
275 myNodeActions.clear();
276 myNodeChildrenActions.clear();
279 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy, @Nullable Object element) {
280 if (!myShowBusyIndicator.asBoolean()) return;
282 boolean canUpdateBusyState = false;
285 if (canYield() || element != null && getTreeStructure().isToBuildChildrenInBackground(element)) {
286 canUpdateBusyState = true;
289 canUpdateBusyState = true;
292 if (!canUpdateBusyState) return;
294 if (myTree instanceof Tree) {
295 final Tree tree = (Tree)myTree;
296 final boolean isBusy = !isReady(true) || forcedBusy;
297 if (isBusy && tree.isShowing()) {
298 tree.setPaintBusy(true);
299 myBusyAlarm.cancelAllRequests();
300 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
303 tree.setPaintBusy(false);
308 private void setHoldSize(boolean holdSize) {
309 if (myTree instanceof Tree) {
310 final Tree tree = (Tree)myTree;
311 tree.setHoldSize(holdSize);
315 private void cleanUpAll() {
316 final long now = System.currentTimeMillis();
317 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
318 for (AbstractTreeUi eachUi : uis) {
319 if (eachUi == null) continue;
320 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
321 if (timeToCleanup == null) continue;
322 if (now >= timeToCleanup.longValue()) {
323 ourUi2Countdown.remove(eachUi);
324 Runnable runnable = new Runnable() {
326 if (!canInitiateNewActivity()) return;
328 myCleanupTask = null;
329 getBuilder().cleanUp();
332 if (isPassthroughMode()) {
336 invokeLaterIfNeeded(runnable, false);
342 protected void doCleanUp() {
343 Runnable cleanup = new Runnable() {
345 if (canInitiateNewActivity()) {
351 if (isPassthroughMode()) {
355 invokeLaterIfNeeded(cleanup, false);
359 public ActionCallback invokeLaterIfNeeded(@NotNull final Runnable runnable, boolean forceEdt) {
360 final ActionCallback result = new ActionCallback();
362 Runnable actual = new Runnable() {
365 result.setRejected();
374 UIUtil.invokeLaterIfNeeded(actual);
376 if (isPassthroughMode() || !isEdt() && !isTreeShowing() && !myWasEverShown) {
380 UIUtil.invokeLaterIfNeeded(actual);
387 public void activate(boolean byShowing) {
388 cancelCurrentCleanupTask();
390 myCanProcessDeferredSelections = true;
391 ourUi2Countdown.remove(this);
393 if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) {
394 getBuilder().updateFromRoot();
397 getUpdater().showNotify();
399 myWasEverShown |= byShowing;
402 private void cancelCurrentCleanupTask() {
403 if (myCleanupTask != null) {
404 myCleanupTask.cancel();
405 myCleanupTask = null;
409 public void deactivate() {
410 getUpdater().hideNotify();
411 myBusyAlarm.cancelAllRequests();
413 if (!myWasEverShown) return;
417 myUpdateFromRootRequested = true;
420 if (getClearOnHideDelay() >= 0) {
421 ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
426 private void sheduleCleanUpAll() {
427 cancelCurrentCleanupTask();
429 myCleanupTask = SimpleTimer.getInstance().setUp(new Runnable() {
433 }, getClearOnHideDelay());
436 public void requestRelease() {
437 myReleaseRequested = true;
438 cancelUpdate().doWhenDone(new Runnable() {
445 public ProgressIndicator getProgress() {
449 private void releaseNow() {
453 myTree.removeTreeExpansionListener(myExpansionListener);
454 myTree.removeTreeSelectionListener(mySelectionListener);
455 myTree.removeFocusListener(myFocusListener);
457 disposeNode(getRootNode());
458 myElementToNodeMap.clear();
459 getUpdater().cancelAllRequests();
460 if (myWorker != null) {
461 myWorker.dispose(true);
464 TREE_NODE_WRAPPER.setValue(null);
465 if (myProgress != null) {
469 cancelCurrentCleanupTask();
474 myTreeStructure = null;
475 myBuilder.releaseUi();
480 myDeferredSelections.clear();
481 myDeferredExpansions.clear();
482 myYeildingDoneRunnables.clear();
489 public boolean isReleased() {
490 return myBuilder == null;
493 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
494 if (!myUnbuiltNodes.contains(node)) return;
495 if (isLoadedInBackground(getElementFor(node))) return;
497 getTreeStructure().commit();
498 addSubtreeToUpdate(node);
499 getUpdater().performUpdate();
502 public final AbstractTreeStructure getTreeStructure() {
503 return myTreeStructure;
506 public final JTree getTree() {
511 private static NodeDescriptor getDescriptorFrom(Object node) {
512 if (node instanceof DefaultMutableTreeNode) {
513 Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
514 if (userObject instanceof NodeDescriptor) {
515 return (NodeDescriptor)userObject;
523 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
524 DefaultMutableTreeNode result = null;
525 if (validateAgainstStructure) {
528 final DefaultMutableTreeNode node = findNode(element, index);
529 if (node == null) break;
531 if (isNodeValidForElement(element, node)) {
540 result = getFirstNode(element);
544 if (result != null && !isNodeInStructure(result)) {
552 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
553 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
556 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
557 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
560 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
561 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
562 final Object parentElement = getElementFor(parent);
563 if (!isInStructure(parentElement)) return false;
565 if (parent instanceof ElementNode) {
566 return ((ElementNode)parent).isValidChild(element);
568 for (int i = 0; i < parent.getChildCount(); i++) {
569 final TreeNode child = parent.getChildAt(i);
570 final Object eachElement = getElementFor(child);
571 if (element.equals(eachElement)) return true;
577 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
580 if (eachParent == null) {
581 valid = eachParentNode == null;
585 if (!eachParent.equals(getElementFor(eachParentNode))) {
590 eachParent = getTreeStructure().getParentElement(eachParent);
591 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
596 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
597 DefaultMutableTreeNode node = null;
598 for (final Object pathElement : path) {
599 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
607 public final void buildNodeForElement(Object element) {
608 getUpdater().performUpdate();
609 DefaultMutableTreeNode node = getNodeForElement(element, false);
611 final List<Object> elements = new ArrayList<Object>();
613 element = getTreeStructure().getParentElement(element);
614 if (element == null) {
617 elements.add(0, element);
620 for (final Object element1 : elements) {
621 node = getNodeForElement(element1, false);
629 public final void buildNodeForPath(Object[] path) {
630 getUpdater().performUpdate();
631 DefaultMutableTreeNode node = null;
632 for (final Object pathElement : path) {
633 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
634 if (node != null && node != path[path.length - 1]) {
640 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
641 myNodeDescriptorComparator = nodeDescriptorComparator;
642 myLastComparatorStamp = -1;
643 getBuilder().queueUpdateFrom(getTreeStructure().getRootElement(), true);
646 protected AbstractTreeBuilder getBuilder() {
650 protected final void initRootNode() {
651 if (myUpdateIfInactive) {
655 myUpdateFromRootRequested = true;
659 private boolean initRootNodeNowIfNeeded(final TreeUpdatePass pass) {
660 boolean wasCleanedUp = false;
661 if (myRootNodeWasQueuedToInitialize) {
662 Object root = getTreeStructure().getRootElement();
663 assert root != null : "Root element cannot be null";
665 Object currentRoot = getElementFor(myRootNode);
667 if (Comparing.equal(root, currentRoot)) return false;
669 Object rootAgain = getTreeStructure().getRootElement();
670 if (root != rootAgain && !root.equals(rootAgain)) {
671 assert false : "getRootElement() if called twice must return either root1 == root2 or root1.equals(root2)";
678 if (myRootNodeWasQueuedToInitialize) return wasCleanedUp;
680 myRootNodeWasQueuedToInitialize = true;
682 final Object rootElement = getTreeStructure().getRootElement();
683 addNodeAction(rootElement, new NodeAction() {
684 public void onReady(final DefaultMutableTreeNode node) {
685 processDeferredActions();
690 final Ref<NodeDescriptor> rootDescriptor = new Ref<NodeDescriptor>(null);
691 final boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(rootElement);
693 Runnable build = new Runnable() {
695 rootDescriptor.set(getTreeStructure().createDescriptor(rootElement, null));
696 getRootNode().setUserObject(rootDescriptor.get());
697 update(rootDescriptor.get(), true);
698 pass.addToUpdated(rootDescriptor.get());
703 Runnable update = new Runnable() {
705 if (getElementFromDescriptor(rootDescriptor.get()) != null) {
706 createMapping(getElementFromDescriptor(rootDescriptor.get()), getRootNode());
710 insertLoadingNode(getRootNode(), true);
712 boolean willUpdate = false;
713 if (isAutoExpand(rootDescriptor.get())) {
714 willUpdate = myUnbuiltNodes.contains(getRootNode());
715 expand(getRootNode(), true);
718 updateNodeChildren(getRootNode(), pass, null, false, false, false, true, true);
720 if (getRootNode().getChildCount() == 0) {
721 myTreeModel.nodeChanged(getRootNode());
727 queueToBackground(build, update, rootDescriptor).doWhenProcessed(new Runnable() {
730 invokeLaterIfNeeded(new Runnable() {
733 myRootNodeInitialized = true;
734 processNodeActionsIfReady(myRootNode);
743 myRootNodeInitialized = true;
744 processNodeActionsIfReady(myRootNode);
750 private boolean isAutoExpand(NodeDescriptor descriptor) {
751 return isAutoExpand(descriptor, true);
754 private boolean isAutoExpand(NodeDescriptor descriptor, boolean validate) {
755 if (descriptor == null) return false;
757 boolean autoExpand = getBuilder().isAutoExpandNode(descriptor);
759 Object element = getElementFromDescriptor(descriptor);
761 autoExpand = validateAutoExpand(autoExpand, element);
764 if (!autoExpand && !myTree.isRootVisible()) {
765 if (element != null && element.equals(getTreeStructure().getRootElement())) return true;
771 private boolean validateAutoExpand(boolean autoExpand, Object element) {
773 int distance = getDistanceToAutoExpandRoot(element);
775 myAutoExpandRoots.add(element);
778 if (distance >= myAutoExpandDepth.asInteger() - 1) {
784 DefaultMutableTreeNode node = getNodeForElement(element, false);
785 autoExpand = isInVisibleAutoExpandChain(node);
791 private boolean isInVisibleAutoExpandChain(DefaultMutableTreeNode child) {
792 TreeNode eachParent = child;
793 while (eachParent != null) {
795 if (myRootNode == eachParent) return true;
797 NodeDescriptor eachDescriptor = getDescriptorFrom((DefaultMutableTreeNode)eachParent);
798 if (!isAutoExpand(eachDescriptor, false)) {
799 TreePath path = getPathFor(eachParent);
800 return myWillBeExpaned.contains(path.getLastPathComponent()) || myTree.isExpanded(path) && myTree.isVisible(path);
802 eachParent = eachParent.getParent();
808 private int getDistanceToAutoExpandRoot(Object element) {
811 Object eachParent = element;
812 while (eachParent != null) {
813 if (myAutoExpandRoots.contains(eachParent)) break;
814 eachParent = getTreeStructure().getParentElement(eachParent);
818 return eachParent != null ? distance : -1;
821 private boolean isAutoExpand(DefaultMutableTreeNode node) {
822 return isAutoExpand(getDescriptorFrom(node));
825 private AsyncResult<Boolean> update(final NodeDescriptor nodeDescriptor, boolean now) {
826 final AsyncResult<Boolean> result = new AsyncResult<Boolean>();
828 if (now || isPassthroughMode()) {
829 result.setDone(_update(nodeDescriptor));
831 Object element = getElementFromDescriptor(nodeDescriptor);
832 boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(element);
834 boolean edt = isEdt();
837 final Ref<Boolean> changes = new Ref<Boolean>(false);
838 queueToBackground(new Runnable() {
840 changes.set(_update(nodeDescriptor));
844 result.setDone(changes.get());
849 result.setDone(_update(nodeDescriptor));
853 if (edt || !myWasEverShown) {
854 result.setDone(_update(nodeDescriptor));
857 invokeLaterIfNeeded(new Runnable() {
859 execute(new Runnable() {
861 result.setDone(_update(nodeDescriptor));
870 result.doWhenDone(new AsyncResult.Handler<Boolean>() {
871 public void run(final Boolean changes) {
873 final long updateStamp = nodeDescriptor.getUpdateCount();
874 invokeLaterIfNeeded(new Runnable() {
876 Object element = nodeDescriptor.getElement();
877 DefaultMutableTreeNode node = getNodeForElement(element, false);
879 TreePath path = getPathFor(node);
880 if (path != null && myTree.isVisible(path)) {
881 updateNodeImageAndPosition(node, false, changes);
894 private boolean _update(final NodeDescriptor nodeDescriptor) {
896 final Ref<Boolean> update = new Ref<Boolean>();
899 execute(new Runnable() {
901 nodeDescriptor.setUpdateCount(nodeDescriptor.getUpdateCount() + 1);
902 update.set(getBuilder().updateNodeDescriptor(nodeDescriptor));
911 catch (IndexNotReadyException e) {
912 warnOnIndexNotReady();
917 public void assertIsDispatchThread() {
918 if (isPassthroughMode()) return;
920 if ((isTreeShowing() || myWasEverShown) && !isEdt()) {
921 LOG.error("Must be in event-dispatch thread");
925 private static boolean isEdt() {
926 return SwingUtilities.isEventDispatchThread();
929 private boolean isTreeShowing() {
933 private void assertNotDispatchThread() {
934 if (isPassthroughMode()) return;
937 LOG.error("Must not be in event-dispatch thread");
941 private void processDeferredActions() {
942 processDeferredActions(myDeferredSelections);
943 processDeferredActions(myDeferredExpansions);
946 private static void processDeferredActions(Set<Runnable> actions) {
947 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
949 for (Runnable runnable : runnables) {
954 //todo: to make real callback
955 public ActionCallback queueUpdate(Object element) {
956 return queueUpdate(element, true);
959 public ActionCallback queueUpdate(Object element, boolean updateStructure) {
960 assertIsDispatchThread();
963 AbstractTreeUpdater updater = getUpdater();
964 if (updater == null) {
965 return new ActionCallback.Rejected();
968 final ActionCallback result = new ActionCallback();
969 Object eachParent = element;
970 while(eachParent != null) {
971 DefaultMutableTreeNode node = getNodeForElement(element, false);
973 addSubtreeToUpdate(node, updateStructure);
977 eachParent = getTreeStructure().getParentElement(eachParent);
980 if (eachParent == null) {
981 addSubtreeToUpdate(getRootNode(), updateStructure);
984 updater.runAfterUpdate(new Runnable() {
991 catch (ProcessCanceledException e) {
992 return new ActionCallback.Rejected();
996 public void doUpdateFromRoot() {
997 updateSubtree(getRootNode(), false);
1000 public ActionCallback doUpdateFromRootCB() {
1001 final ActionCallback cb = new ActionCallback();
1002 getUpdater().runAfterUpdate(new Runnable() {
1007 updateSubtree(getRootNode(), false);
1011 public final void updateSubtree(DefaultMutableTreeNode node, boolean canSmartExpand) {
1012 updateSubtree(new TreeUpdatePass(node), canSmartExpand);
1015 public final void updateSubtree(TreeUpdatePass pass, boolean canSmartExpand) {
1016 if (getUpdater() != null) {
1017 getUpdater().addSubtreeToUpdate(pass);
1020 updateSubtreeNow(pass, canSmartExpand);
1024 final void updateSubtreeNow(TreeUpdatePass pass, boolean canSmartExpand) {
1025 maybeSetBusyAndScheduleWaiterForReady(true, getElementFor(pass.getNode()));
1028 boolean consumed = initRootNodeNowIfNeeded(pass);
1029 if (consumed) return;
1031 final DefaultMutableTreeNode node = pass.getNode();
1033 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
1035 if (pass.isUpdateStructure()) {
1036 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
1038 boolean forceUpdate = true;
1039 TreePath path = getPathFor(node);
1040 boolean invisible = !myTree.isExpanded(path) && (path.getParentPath() == null || !myTree.isExpanded(path.getParentPath()));
1042 if (invisible && myUnbuiltNodes.contains(node)) {
1043 forceUpdate = false;
1046 updateNodeChildren(node, pass, null, false, canSmartExpand, forceUpdate, false, pass.isUpdateChildren());
1052 private void updateRow(final int row, final TreeUpdatePass pass) {
1053 invokeLaterIfNeeded(new Runnable() {
1056 if (row >= getTree().getRowCount()) return;
1058 TreePath path = getTree().getPathForRow(row);
1060 final NodeDescriptor descriptor = getDescriptorFrom(path.getLastPathComponent());
1061 if (descriptor != null) {
1062 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
1063 maybeYeild(new ActiveRunnable() {
1065 public ActionCallback run() {
1066 ActionCallback result = new ActionCallback();
1067 update(descriptor, false).doWhenDone(new Runnable() {
1070 updateRow(row + 1, pass);
1082 private boolean isToBuildInBackground(NodeDescriptor descriptor) {
1083 return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor));
1087 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
1088 if (myUpdaterState != null && myUpdaterState.equals(state)) return state;
1090 final UpdaterTreeState oldState = myUpdaterState;
1091 if (oldState == null) {
1092 myUpdaterState = state;
1096 oldState.addAll(state);
1101 protected void doUpdateNode(final DefaultMutableTreeNode node) {
1102 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
1103 final NodeDescriptor descriptor = getDescriptorFrom(node);
1104 final Object prevElement = getElementFromDescriptor(descriptor);
1105 if (prevElement == null) return;
1106 update(descriptor, false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1107 public void run(Boolean changes) {
1108 if (!isValid(descriptor)) {
1109 if (isInStructure(prevElement)) {
1110 getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement));
1115 updateNodeImageAndPosition(node, false, changes);
1121 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
1122 return getBuilder().getTreeStructureElement(descriptor);
1125 private void updateNodeChildren(final DefaultMutableTreeNode node,
1126 final TreeUpdatePass pass,
1127 @Nullable final LoadedChildren loadedChildren,
1128 final boolean forcedNow,
1129 final boolean toSmartExpand,
1130 final boolean forceUpdate,
1131 final boolean descriptorIsUpToDate, final boolean updateChildren) {
1133 removeFromCancelled(node);
1135 execute(new Runnable() {
1139 AbstractTreeStructure treeStructure = getTreeStructure();
1140 if (treeStructure.hasSomethingToCommit()) treeStructure.commit();
1142 final NodeDescriptor descriptor = getDescriptorFrom(node);
1143 if (descriptor == null) {
1144 removeFromUnbuilt(node);
1145 removeLoading(node, true);
1149 boolean descriptorIsReady = descriptorIsUpToDate || pass.isUpdated(descriptor);
1151 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node);
1152 final boolean wasLeaf = node.getChildCount() == 0;
1155 boolean bgBuild = isToBuildInBackground(descriptor);
1156 boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded;
1158 if (notRequiredToUpdateChildren && forceUpdate && !wasExpanded) {
1159 boolean alwaysPlus = getBuilder().isAlwaysShowPlus(descriptor);
1160 if (alwaysPlus && wasLeaf) {
1161 notRequiredToUpdateChildren = false;
1164 notRequiredToUpdateChildren = alwaysPlus;
1165 if (notRequiredToUpdateChildren && !wasExpanded && !myUnbuiltNodes.contains(node)) {
1166 removeChildren(node);
1171 final Ref<LoadedChildren> preloaded = new Ref<LoadedChildren>(loadedChildren);
1173 if (notRequiredToUpdateChildren) {
1174 if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) {
1175 insertLoadingNode(node, true);
1178 if (!descriptorIsReady) {
1179 update(descriptor, false);
1187 if (myUnbuiltNodes.contains(node)) {
1188 if (!descriptorIsReady) {
1189 update(descriptor, true);
1190 descriptorIsReady = true;
1193 if (processAlwaysLeaf(node) || !updateChildren) return;
1195 Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null);
1197 if (unbuilt.getFirst()) return;
1198 preloaded.set(unbuilt.getSecond());
1204 final boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded);
1206 if (!forcedNow && isToBuildInBackground(descriptor)) {
1207 boolean alwaysLeaf = processAlwaysLeaf(node);
1208 queueBackgroundUpdate(
1209 new UpdateInfo(descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorIsReady,
1210 !alwaysLeaf && updateChildren), node);
1214 if (!descriptorIsReady) {
1215 update(descriptor, false).doWhenDone(new Runnable() {
1217 if (processAlwaysLeaf(node) || !updateChildren) return;
1218 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
1223 if (processAlwaysLeaf(node) || !updateChildren) return;
1225 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
1230 if (!isReleased()) {
1231 processNodeActionsIfReady(node);
1238 private boolean processAlwaysLeaf(DefaultMutableTreeNode node) {
1239 Object element = getElementFor(node);
1240 NodeDescriptor desc = getDescriptorFrom(node);
1242 if (desc == null) return false;
1244 if (getTreeStructure().isAlwaysLeaf(element)) {
1245 removeFromUnbuilt(node);
1246 removeLoading(node, true);
1248 if (node.getChildCount() > 0) {
1249 final TreeNode[] children = new TreeNode[node.getChildCount()];
1250 for (int i = 0; i < node.getChildCount(); i++) {
1251 children[i] = node.getChildAt(i);
1254 if (isSelectionInside(node)) {
1255 addSelectionPath(getPathFor(node), true, Condition.TRUE, null);
1258 processInnerChange(new Runnable() {
1260 for (TreeNode each : children) {
1261 removeNodeFromParent((MutableTreeNode)each, true);
1262 disposeNode((DefaultMutableTreeNode)each);
1268 removeFromUnbuilt(node);
1269 desc.setWasDeclaredAlwaysLeaf(true);
1270 processNodeActionsIfReady(node);
1274 boolean wasLeaf = desc.isWasDeclaredAlwaysLeaf();
1275 desc.setWasDeclaredAlwaysLeaf(false);
1278 insertLoadingNode(node, true);
1285 private boolean isChildNodeForceUpdate(DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) {
1286 TreePath path = getPathFor(node);
1287 return parentForceUpdate && (parentExpanded || myTree.isExpanded(path));
1290 private void updateNodeChildrenNow(final DefaultMutableTreeNode node,
1291 final TreeUpdatePass pass,
1292 final LoadedChildren preloadedChildren,
1293 final boolean toSmartExpand,
1294 final boolean wasExpanded,
1295 final boolean wasLeaf,
1296 final boolean forceUpdate) {
1297 if (isUpdatingChildrenNow(node)) return;
1299 if (!canInitiateNewActivity()) {
1300 throw new ProcessCanceledException();
1303 final NodeDescriptor descriptor = getDescriptorFrom(node);
1305 final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren);
1306 final LoadedChildren loadedChildren =
1307 preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray());
1310 addToUpdatingChildren(node);
1311 pass.setCurrentNode(node);
1313 final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
1315 removeFromUnbuilt(node);
1317 processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren)
1318 .doWhenDone(new Runnable() {
1320 if (isDisposed(node)) {
1321 removeFromUpdatingChildren(node);
1325 removeLoading(node, false);
1327 final boolean expanded = isExpanded(node, wasExpanded);
1330 myWillBeExpaned.add(node);
1333 myWillBeExpaned.remove(node);
1336 collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren)
1337 .doWhenDone(new AsyncResult.Handler<ArrayList<TreeNode>>() {
1338 public void run(ArrayList<TreeNode> nodesToInsert) {
1339 insertNodesInto(nodesToInsert, node);
1340 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
1341 removeLoading(node, false);
1342 removeFromUpdatingChildren(node);
1344 if (node.getChildCount() > 0) {
1346 expand(node, canSmartExpand);
1350 if (!canInitiateNewActivity()) {
1351 throw new ProcessCanceledException();
1354 final Object element = getElementFor(node);
1355 addNodeAction(element, new NodeAction() {
1356 public void onReady(final DefaultMutableTreeNode node) {
1357 removeLoading(node, false);
1361 processNodeActionsIfReady(node);
1363 }).doWhenProcessed(new Runnable() {
1365 myWillBeExpaned.remove(node);
1366 removeFromUpdatingChildren(node);
1367 processNodeActionsIfReady(node);
1371 }).doWhenRejected(new Runnable() {
1373 removeFromUpdatingChildren(node);
1374 processNodeActionsIfReady(node);
1379 private boolean isDisposed(DefaultMutableTreeNode node) {
1380 return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
1383 private void expandSilently(TreePath path) {
1384 assertIsDispatchThread();
1387 mySilentExpand = path;
1388 getTree().expandPath(path);
1391 mySilentExpand = null;
1395 private void addSelectionSilently(TreePath path) {
1396 assertIsDispatchThread();
1399 mySilentSelect = path;
1400 getTree().getSelectionModel().addSelectionPath(path);
1403 mySilentSelect = null;
1407 private void expand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1408 expand(new TreePath(node.getPath()), canSmartExpand);
1411 private void expand(final TreePath path, boolean canSmartExpand) {
1412 if (path == null) return;
1415 final Object last = path.getLastPathComponent();
1416 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
1417 final boolean isRoot = last == myTree.getModel().getRoot();
1418 final TreePath parent = path.getParentPath();
1419 if (isRoot && !myTree.isExpanded(path)) {
1420 if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) {
1421 insertLoadingNode((DefaultMutableTreeNode)last, false);
1423 expandPath(path, canSmartExpand);
1425 else if (myTree.isExpanded(path) ||
1426 isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last) && !isCancelled(last)) {
1427 if (last instanceof DefaultMutableTreeNode) {
1428 processNodeActionsIfReady((DefaultMutableTreeNode)last);
1432 if (isLeaf && (myUnbuiltNodes.contains(last) || isCancelled(last))) {
1433 insertLoadingNode((DefaultMutableTreeNode)last, true);
1434 expandPath(path, canSmartExpand);
1436 else if (isLeaf && parent != null) {
1437 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
1438 if (parentNode != null) {
1439 addToUnbuilt(parentNode);
1441 expandPath(parent, canSmartExpand);
1444 expandPath(path, canSmartExpand);
1449 private void addToUnbuilt(DefaultMutableTreeNode node) {
1450 myUnbuiltNodes.add(node);
1453 private void removeFromUnbuilt(DefaultMutableTreeNode node) {
1454 myUnbuiltNodes.remove(node);
1457 private Pair<Boolean, LoadedChildren> processUnbuilt(final DefaultMutableTreeNode node,
1458 final NodeDescriptor descriptor,
1459 final TreeUpdatePass pass,
1460 final boolean isExpanded,
1461 final LoadedChildren loadedChildren) {
1462 final Ref<Pair<Boolean, LoadedChildren>> result = new Ref<Pair<Boolean, LoadedChildren>>();
1464 execute(new Runnable() {
1466 if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) {
1467 result.set(new Pair<Boolean, LoadedChildren>(true, null));
1471 final Object element = getElementFor(node);
1473 addToUpdatingChildren(node);
1476 final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element));
1480 if (children.getElements().isEmpty()) {
1481 removeFromUnbuilt(node);
1482 removeLoading(node, true);
1486 if (isAutoExpand(node)) {
1487 addNodeAction(getElementFor(node), new NodeAction() {
1488 public void onReady(final DefaultMutableTreeNode node) {
1489 final TreePath path = new TreePath(node.getPath());
1490 if (getTree().isExpanded(path) || children.getElements().isEmpty()) {
1491 removeLoading(node, false);
1494 maybeYeild(new ActiveRunnable() {
1495 public ActionCallback run() {
1496 expand(element, null);
1497 return new ActionCallback.Done();
1507 removeFromUpdatingChildren(node);
1509 processNodeActionsIfReady(node);
1511 result.set(new Pair<Boolean, LoadedChildren>(processed, children));
1514 removeFromUpdatingChildren(node);
1519 return result.get();
1522 private boolean removeIfLoading(TreeNode node) {
1523 if (isLoadingNode(node)) {
1524 moveSelectionToParentIfNeeded(node);
1525 removeNodeFromParent((MutableTreeNode)node, false);
1532 private void moveSelectionToParentIfNeeded(TreeNode node) {
1533 TreePath path = getPathFor(node);
1534 if (myTree.getSelectionModel().isPathSelected(path)) {
1535 TreePath parentPath = path.getParentPath();
1536 myTree.getSelectionModel().removeSelectionPath(path);
1537 if (parentPath != null) {
1538 myTree.getSelectionModel().addSelectionPath(parentPath);
1543 //todo [kirillk] temporary consistency check
1544 private Object[] getChildrenFor(final Object element) {
1545 final Ref<Object[]> passOne = new Ref<Object[]>();
1548 execute(new Runnable() {
1550 passOne.set(getTreeStructure().getChildElements(element));
1554 catch (IndexNotReadyException e) {
1555 warnOnIndexNotReady();
1556 return ArrayUtil.EMPTY_OBJECT_ARRAY;
1562 if (!Registry.is("ide.tree.checkStructure")) return passOne.get();
1564 final Object[] passTwo = getTreeStructure().getChildElements(element);
1566 final HashSet<Object> two = new HashSet<Object>(Arrays.asList(passTwo));
1568 if (passOne.get().length != passTwo.length) {
1570 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1574 for (Object eachInOne : passOne.get()) {
1575 if (!two.contains(eachInOne)) {
1577 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1584 return passOne.get();
1587 private void warnOnIndexNotReady() {
1588 if (!myWasEverIndexNotReady) {
1589 myWasEverIndexNotReady = true;
1590 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
1594 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert,
1595 TreeUpdatePass pass,
1596 boolean canSmartExpand,
1597 boolean forceUpdate) {
1598 for (TreeNode aNodesToInsert : nodesToInsert) {
1599 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
1600 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true, true);
1604 private ActionCallback processExistingNodes(final DefaultMutableTreeNode node,
1605 final MutualMap<Object, Integer> elementToIndexMap,
1606 final TreeUpdatePass pass,
1607 final boolean canSmartExpand,
1608 final boolean forceUpdate,
1609 final boolean wasExpaned,
1610 final LoadedChildren preloaded) {
1612 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
1613 return maybeYeild(new ActiveRunnable() {
1614 public ActionCallback run() {
1615 if (pass.isExpired()) return new ActionCallback.Rejected();
1616 if (childNodes.isEmpty()) return new ActionCallback.Done();
1619 final ActionCallback result = new ActionCallback(childNodes.size());
1621 for (TreeNode each : childNodes) {
1622 final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each;
1623 if (isLoadingNode(eachChild)) {
1628 final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned);
1630 maybeYeild(new ActiveRunnable() {
1632 public ActionCallback run() {
1633 NodeDescriptor descriptor = preloaded != null ? preloaded.getDescriptor(getElementFor(eachChild)) : null;
1634 NodeDescriptor descriptorFromNode = getDescriptorFrom(eachChild);
1635 if (descriptor != null) {
1636 eachChild.setUserObject(descriptor);
1637 if (descriptorFromNode != null) {
1638 descriptor.setChildrenSortingStamp(descriptorFromNode.getChildrenSortingStamp());
1641 descriptor = descriptorFromNode;
1644 return processExistingNode(eachChild, descriptor, node, elementToIndexMap, pass, canSmartExpand,
1645 childForceUpdate, preloaded);
1647 }, pass, node).notify(result);
1649 if (result.isRejected()) {
1659 private boolean isRerunNeeded(TreeUpdatePass pass) {
1660 if (pass.isExpired() || !canInitiateNewActivity()) return false;
1662 final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
1664 return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
1667 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
1668 final ActionCallback result = new ActionCallback();
1670 if (isRerunNeeded(pass)) {
1671 getUpdater().requeue(pass);
1672 result.setRejected();
1675 if (isToYieldUpdateFor(node)) {
1676 pass.setCurrentNode(node);
1677 boolean wasRun = yieldAndRun(new Runnable() {
1679 if (pass.isExpired()) {
1680 result.setRejected();
1684 if (isRerunNeeded(pass)) {
1685 runDone(new Runnable() {
1687 if (!pass.isExpired()) {
1688 getUpdater().requeue(pass);
1692 result.setRejected();
1696 execute(processRunnable).notify(result);
1698 catch (ProcessCanceledException e) {
1700 result.setRejected();
1707 result.setRejected();
1712 execute(processRunnable).notify(result);
1714 catch (ProcessCanceledException e) {
1716 result.setRejected();
1725 private ActionCallback execute(final ActiveRunnable runnable) throws ProcessCanceledException {
1726 final ActionCallback result = new ActionCallback();
1727 execute(new Runnable() {
1729 runnable.run().notify(result);
1735 private void execute(Runnable runnable) {
1736 execute(runnable, null);
1739 private void execute(Runnable runnable, @Nullable DefaultMutableTreeNode node) throws ProcessCanceledException {
1741 if (!canInitiateNewActivity()) {
1742 throw new ProcessCanceledException();
1747 if (!canInitiateNewActivity()) {
1748 throw new ProcessCanceledException();
1751 catch (ProcessCanceledException e) {
1753 addToCancelled(node);
1755 if (!isReleased()) {
1756 setCancelRequested(true);
1763 private boolean canInitiateNewActivity() {
1764 return !isCancelProcessed() && !myReleaseRequested && !isReleased();
1767 private ActionCallback resetToReady() {
1768 final ActionCallback result = new ActionCallback();
1775 if (myResettingToReadyNow.get()) {
1776 _getReady().notify(result);
1780 myResettingToReadyNow.set(true);
1782 invokeLaterIfNeeded(new Runnable() {
1784 if (!myResettingToReadyNow.get()) {
1789 Progressive[] progressives = myBatchIndicators.keySet().toArray(new Progressive[myBatchIndicators.size()]);
1790 for (Progressive each : progressives) {
1791 myBatchIndicators.remove(each).cancel();
1792 myBatchCallbacks.remove(each).setRejected();
1795 resetToReadyNow().notify(result);
1802 private ActionCallback resetToReadyNow() {
1803 if (isReleased()) return new ActionCallback.Rejected();
1805 assertIsDispatchThread();
1807 DefaultMutableTreeNode[] uc = myUpdatingChildren.toArray(new DefaultMutableTreeNode[myUpdatingChildren.size()]);
1808 for (DefaultMutableTreeNode each : uc) {
1809 resetIncompleteNode(each);
1813 Object[] bg = myLoadedInBackground.keySet().toArray(new Object[myLoadedInBackground.size()]);
1814 for (Object each : bg) {
1815 final DefaultMutableTreeNode node = getNodeForElement(each, false);
1817 resetIncompleteNode(node);
1821 myUpdaterState = null;
1822 getUpdater().reset();
1825 myYeildingNow = false;
1826 myYeildingPasses.clear();
1827 myYeildingDoneRunnables.clear();
1829 myNodeActions.clear();
1830 myNodeChildrenActions.clear();
1832 myUpdatingChildren.clear();
1833 myLoadedInBackground.clear();
1835 myDeferredExpansions.clear();
1836 myDeferredSelections.clear();
1838 ActionCallback result = _getReady();
1839 result.doWhenDone(new Runnable() {
1841 myResettingToReadyNow.set(false);
1842 setCancelRequested(false);
1851 public void addToCancelled(DefaultMutableTreeNode node) {
1852 myCancelledBuild.put(node, node);
1855 public void removeFromCancelled(DefaultMutableTreeNode node) {
1856 myCancelledBuild.remove(node);
1859 public boolean isCancelled(Object node) {
1860 return node instanceof DefaultMutableTreeNode && myCancelledBuild.containsKey(node);
1863 private void resetIncompleteNode(@NotNull DefaultMutableTreeNode node) {
1864 if (myReleaseRequested) return;
1866 addToCancelled(node);
1868 if (!isExpanded(node, false)) {
1869 node.removeAllChildren();
1870 if (!getTreeStructure().isAlwaysLeaf(getElementFor(node))) {
1871 insertLoadingNode(node, true);
1875 removeFromUnbuilt(node);
1876 removeLoading(node, true);
1880 private boolean yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
1881 myYeildingPasses.add(pass);
1882 myYeildingNow = true;
1883 yield(new Runnable() {
1885 if (isReleased()) return;
1887 runOnYieldingDone(new Runnable() {
1889 if (isReleased()) return;
1891 executeYieldingRequest(runnable, pass);
1900 public boolean isYeildingNow() {
1901 return myYeildingNow;
1904 private boolean hasSheduledUpdates() {
1905 return getUpdater().hasNodesToUpdate();
1908 public boolean isReady() {
1909 return isReady(false);
1912 public boolean isCancelledReady() {
1913 return isReady(false) && !myCancelledBuild.isEmpty();
1916 public boolean isReady(boolean attempt) {
1917 Boolean ready = _isReady(attempt);
1918 return ready != null && ready.booleanValue();
1922 public Boolean _isReady(boolean attempt) {
1923 if (attempt && myLockWasAcquired.get()) return false;
1925 Boolean ready = checkValue(new Computable<Boolean>() {
1927 public Boolean compute() {
1928 return Boolean.valueOf(isIdle() && !hasPendingWork() && !isNodeActionsPending());
1932 return ready != null && ready.booleanValue();
1935 private Boolean checkValue(Computable<Boolean> computable, boolean attempt, Boolean defaultValue) {
1936 boolean toRelease = true;
1939 if (!attemptLock()) {
1941 return defaultValue != null ? defaultValue : computable.compute();
1946 return computable.compute();
1948 catch (InterruptedException e) {
1950 return defaultValue;
1958 public String getStatus() {
1959 return "isReady=" + isReady() + "\n" +
1960 " isIdle=" + isIdle() + "\n" +
1961 " isYeildingNow=" + isYeildingNow() + "\n" +
1962 " isWorkerBusy=" + isWorkerBusy() + "\n" +
1963 " hasUpdatingChildrenNow=" + hasUpdatingChildrenNow() + "\n" +
1964 " isLoadingInBackgroundNow=" + isLoadingInBackgroundNow() + "\n" +
1965 " hasPendingWork=" + hasPendingWork() + "\n" +
1966 " hasNodesToUpdate=" + hasNodesToUpdate() + "\n" +
1967 " updaterState=" + myUpdaterState + "\n" +
1968 " hasScheduledUpdates=" + hasSheduledUpdates() + "\n" +
1969 " isPostponedMode=" + getUpdater().isInPostponeMode() + "\n" +
1970 " nodeActions=" + myNodeActions.keySet() + "\n" +
1971 " nodeChildrenActions=" + myNodeChildrenActions.keySet() + "\n" +
1972 "isReleased=" + isReleased() + "\n" +
1973 " isReleaseRequested=" + isReleaseRequested() + "\n" +
1974 "isCancelProcessed=" + isCancelProcessed() + "\n" +
1975 " isCancelRequested=" + myCancelRequest + "\n" +
1976 " isResettingToReadyNow=" + myResettingToReadyNow + "\n" +
1977 "canInitiateNewActivity=" + canInitiateNewActivity() +
1978 "batchIndicators=" + myBatchIndicators;
1981 public boolean hasPendingWork() {
1982 return hasNodesToUpdate() ||
1983 myUpdaterState != null && myUpdaterState.isProcessingNow() ||
1984 hasSheduledUpdates() && !getUpdater().isInPostponeMode();
1987 public boolean isIdle() {
1988 return !isYeildingNow() && !isWorkerBusy() && !hasUpdatingChildrenNow() && !isLoadingInBackgroundNow();
1991 private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
1994 myYeildingPasses.remove(pass);
1996 if (!canInitiateNewActivity()) {
1997 throw new ProcessCanceledException();
2003 if (!isReleased()) {
2004 maybeYeildingFinished();
2008 catch (ProcessCanceledException e) {
2013 private void maybeYeildingFinished() {
2014 if (myYeildingPasses.isEmpty()) {
2015 myYeildingNow = false;
2016 flushPendingNodeActions();
2021 assertIsDispatchThread();
2023 if (isReleased()) return;
2025 Boolean ready = _isReady(true);
2026 if (ready != null && ready.booleanValue()) {
2027 myRevalidatedObjects.clear();
2029 setCancelRequested(false);
2030 myResettingToReadyNow.set(false);
2032 myInitialized.setDone();
2034 if (canInitiateNewActivity()) {
2035 if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) {
2036 UpdaterTreeState oldState = myUpdaterState;
2037 if (!myUpdaterState.restore(null)) {
2038 setUpdaterState(oldState);
2049 if (myTree.isShowing()) {
2050 if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
2051 TreeUtil.ensureSelection(myTree);
2055 if (myInitialized.isDone()) {
2056 if (isReleaseRequested() || isCancelProcessed()) {
2057 myBusyObject.onReady(AbstractTreeUi.this);
2059 myBusyObject.onReady();
2063 if (canInitiateNewActivity()) {
2064 TreePath[] selection = getTree().getSelectionPaths();
2065 Rectangle visible = getTree().getVisibleRect();
2066 if (selection != null) {
2067 for (TreePath each : selection) {
2068 Rectangle bounds = getTree().getPathBounds(each);
2069 if (bounds != null && (visible.contains(bounds) || visible.intersects(bounds))) {
2070 getTree().repaint(bounds);
2075 } else if (ready == null) {
2076 scheduleMaybeReady();
2080 private void scheduleMaybeReady() {
2081 myMaybeReady.cancelAllRequests();
2082 myMaybeReady.addRequest(myMaybeReadyRunnable, Registry.intValue("ide.tree.waitForReadySchedule"));
2085 private void flushPendingNodeActions() {
2086 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
2087 myPendingNodeActions.clear();
2089 for (DefaultMutableTreeNode each : nodes) {
2090 processNodeActionsIfReady(each);
2093 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
2094 for (Runnable each : actions) {
2095 if (!isYeildingNow()) {
2096 myYeildingDoneRunnables.remove(each);
2104 protected void runOnYieldingDone(Runnable onDone) {
2105 getBuilder().runOnYeildingDone(onDone);
2108 protected void yield(Runnable runnable) {
2109 getBuilder().yield(runnable);
2112 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
2113 return canYield() && getBuilder().isToYieldUpdateFor(node);
2116 private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor,
2117 @Nullable LoadedChildren preloadedChildren) {
2118 MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
2119 final Object element = getBuilder().getTreeStructureElement(descriptor);
2120 if (!isValid(element)) return elementToIndexMap;
2123 List<Object> children = preloadedChildren != null
2124 ? preloadedChildren.getElements()
2125 : Arrays.asList(getChildrenFor(element));
2127 for (Object child : children) {
2128 if (!isValid(child)) continue;
2129 elementToIndexMap.put(child, Integer.valueOf(index));
2132 return elementToIndexMap;
2135 private void expand(final DefaultMutableTreeNode node,
2136 final NodeDescriptor descriptor,
2137 final boolean wasLeaf,
2138 final boolean canSmartExpand) {
2139 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
2140 alarm.addRequest(new Runnable() {
2142 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
2144 }, WAIT_CURSOR_DELAY);
2146 if (wasLeaf && isAutoExpand(descriptor)) {
2147 expand(node, canSmartExpand);
2150 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
2151 for (TreeNode node1 : nodes) {
2152 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
2153 if (isLoadingNode(childNode)) continue;
2154 NodeDescriptor childDescr = getDescriptorFrom(childNode);
2155 if (isAutoExpand(childDescr)) {
2156 addNodeAction(getElementFor(childNode), new NodeAction() {
2157 public void onReady(DefaultMutableTreeNode node) {
2158 expand(childNode, canSmartExpand);
2161 addSubtreeToUpdate(childNode);
2165 int n = alarm.cancelAllRequests();
2167 myTree.setCursor(Cursor.getDefaultCursor());
2171 public static boolean isLoadingNode(final Object node) {
2172 return node instanceof LoadingNode;
2175 private AsyncResult<ArrayList<TreeNode>> collectNodesToInsert(final NodeDescriptor descriptor,
2176 final MutualMap<Object, Integer> elementToIndexMap,
2177 final DefaultMutableTreeNode parent,
2178 final boolean addLoadingNode,
2179 @NotNull final LoadedChildren loadedChildren) {
2180 final AsyncResult<ArrayList<TreeNode>> result = new AsyncResult<ArrayList<TreeNode>>();
2182 final ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
2183 final Collection<Object> allElements = elementToIndexMap.getKeys();
2185 final ActionCallback processingDone = new ActionCallback(allElements.size());
2187 for (final Object child : allElements) {
2188 Integer index = elementToIndexMap.getValue(child);
2189 final Ref<NodeDescriptor> childDescr = new Ref<NodeDescriptor>(loadedChildren.getDescriptor(child));
2190 boolean needToUpdate = false;
2191 if (childDescr.get() == null) {
2192 childDescr.set(getTreeStructure().createDescriptor(child, descriptor));
2193 needToUpdate = true;
2196 if (childDescr.get() == null) {
2197 processingDone.setDone();
2201 if (index == null) {
2202 index = Integer.MAX_VALUE;
2203 needToUpdate = true;
2206 childDescr.get().setIndex(index.intValue());
2208 final ActionCallback update = new ActionCallback();
2210 update(childDescr.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
2211 public void run(Boolean changes) {
2212 loadedChildren.putDescriptor(child, childDescr.get(), changes);
2221 update.doWhenDone(new Runnable() {
2223 Object element = getElementFromDescriptor(childDescr.get());
2224 if (element == null) {
2225 processingDone.setDone();
2228 DefaultMutableTreeNode node = getNodeForElement(element, false);
2229 if (node == null || node.getParent() != parent) {
2230 final DefaultMutableTreeNode childNode = createChildNode(childDescr.get());
2231 if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr.get())) {
2232 insertLoadingNode(childNode, true);
2235 addToUnbuilt(childNode);
2237 nodesToInsert.add(childNode);
2238 createMapping(element, childNode);
2240 processingDone.setDone();
2246 processingDone.doWhenDone(new Runnable() {
2248 result.setDone(nodesToInsert);
2255 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
2256 return new ElementNode(this, descriptor);
2259 protected boolean canYield() {
2260 return myCanYield && myYeildingUpdate.asBoolean();
2263 public long getClearOnHideDelay() {
2264 return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
2267 public ActionCallback getInitialized() {
2268 return myInitialized;
2271 public ActionCallback getReady(Object requestor) {
2272 return myBusyObject.getReady(requestor);
2275 private ActionCallback _getReady() {
2276 return getReady(this);
2279 private void addToUpdatingChildren(DefaultMutableTreeNode node) {
2280 synchronized (myUpdatingChildren) {
2281 myUpdatingChildren.add(node);
2285 private void removeFromUpdatingChildren(DefaultMutableTreeNode node) {
2286 synchronized (myUpdatingChildren) {
2287 myUpdatingChildren.remove(node);
2291 public boolean isUpdatingChildrenNow(DefaultMutableTreeNode node) {
2292 synchronized (myUpdatingChildren) {
2293 return myUpdatingChildren.contains(node);
2297 public boolean isParentUpdatingChildrenNow(DefaultMutableTreeNode node) {
2298 synchronized (myUpdatingChildren) {
2299 DefaultMutableTreeNode eachParent = (DefaultMutableTreeNode)node.getParent();
2300 while (eachParent != null) {
2301 if (myUpdatingChildren.contains(eachParent)) return true;
2303 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
2310 boolean hasUpdatingChildrenNow() {
2311 synchronized (myUpdatingChildren) {
2312 return !myUpdatingChildren.isEmpty();
2316 public Map<Object, List<NodeAction>> getNodeActions() {
2317 return myNodeActions;
2320 public List<Object> getLoadedChildrenFor(Object element) {
2321 List<Object> result = new ArrayList<Object>();
2323 DefaultMutableTreeNode node = getNodeForElement(element, false);
2325 for (int i = 0; i < node.getChildCount(); i++) {
2326 TreeNode each = node.getChildAt(i);
2327 if (isLoadingNode(each)) continue;
2329 result.add(getElementFor(each));
2336 public boolean hasNodesToUpdate() {
2337 return getUpdater().hasNodesToUpdate();
2340 public List<Object> getExpandedElements() {
2341 final List<Object> result = new ArrayList<Object>();
2342 if (isReleased()) return result;
2344 final Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode()));
2345 if (enumeration != null) {
2346 while (enumeration.hasMoreElements()) {
2347 TreePath each = enumeration.nextElement();
2348 Object eachElement = getElementFor(each.getLastPathComponent());
2349 if (eachElement != null) {
2350 result.add(eachElement);
2358 public ActionCallback cancelUpdate() {
2359 if (isReleased()) return new ActionCallback.Rejected();
2361 setCancelRequested(true);
2363 final ActionCallback done = new ActionCallback();
2365 invokeLaterIfNeeded(new Runnable() {
2372 if (myResettingToReadyNow.get()) {
2373 _getReady().notify(done);
2374 } else if (isReady()) {
2378 if (isIdle() && hasPendingWork()) {
2382 _getReady().notify(done);
2390 if (isEdt() || isPassthroughMode()) {
2397 private void setCancelRequested(boolean requested) {
2399 if (isUnitTestingMode()) {
2404 myCancelRequest.set(requested);
2406 catch (InterruptedException ignored) {
2413 private boolean attemptLock() throws InterruptedException {
2414 myLockWasAcquired.set(myStateLock.tryLock(Registry.intValue("ide.tree.uiLockAttempt"), TimeUnit.MILLISECONDS));
2415 return myLockWasAcquired.get();
2418 private void acquireLock() {
2420 myLockWasAcquired.set(true);
2423 private void releaseLock() {
2424 myStateLock.unlock();
2425 myLockWasAcquired.set(false);
2429 public ActionCallback batch(final Progressive progressive) {
2430 assertIsDispatchThread();
2432 EmptyProgressIndicator indicator = new EmptyProgressIndicator();
2433 final ActionCallback callback = new ActionCallback();
2435 myBatchIndicators.put(progressive, indicator);
2436 myBatchCallbacks.put(progressive, callback);
2439 progressive.run(indicator);
2441 catch (ProcessCanceledException e) {
2442 resetToReadyNow().doWhenProcessed(new Runnable() {
2444 callback.setRejected();
2450 if (isReleased()) return new ActionCallback.Rejected();
2452 _getReady().doWhenDone(new Runnable() {
2454 if (myBatchIndicators.containsKey(progressive)) {
2455 ProgressIndicator indicator = myBatchIndicators.remove(progressive);
2456 myBatchCallbacks.remove(progressive);
2458 if (indicator.isCanceled()) {
2459 callback.setRejected();
2464 callback.setRejected();
2476 public boolean isCancelProcessed() {
2477 Computable<Boolean> computable = new Computable<Boolean>() {
2479 public Boolean compute() {
2480 return Boolean.valueOf(myCancelRequest.get() || myResettingToReadyNow.get());
2483 //Boolean processed = checkValue(computable, true, null);
2484 Boolean processed = computable.compute();
2485 return processed != null && processed.booleanValue();
2488 public boolean isToPaintSelection() {
2489 return isReady(true) || !mySelectionIsAdjusted;
2492 public boolean isReleaseRequested() {
2493 return myReleaseRequested;
2496 public void executeUserRunnable(Runnable runnable) {
2498 myUserRunnables.add(runnable);
2502 myUserRunnables.remove(runnable);
2506 static class ElementNode extends DefaultMutableTreeNode {
2508 Set<Object> myElements = new HashSet<Object>();
2509 AbstractTreeUi myUi;
2511 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
2517 public void insert(final MutableTreeNode newChild, final int childIndex) {
2518 super.insert(newChild, childIndex);
2519 final Object element = myUi.getElementFor(newChild);
2520 if (element != null) {
2521 myElements.add(element);
2526 public void remove(final int childIndex) {
2527 final TreeNode node = getChildAt(childIndex);
2528 super.remove(childIndex);
2529 final Object element = myUi.getElementFor(node);
2530 if (element != null) {
2531 myElements.remove(element);
2535 boolean isValidChild(Object childElement) {
2536 return myElements.contains(childElement);
2540 public String toString() {
2541 return String.valueOf(getUserObject());
2545 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
2546 return getUpdatingParent(kid) != null;
2549 private DefaultMutableTreeNode getUpdatingParent(DefaultMutableTreeNode kid) {
2550 DefaultMutableTreeNode eachParent = kid;
2551 while (eachParent != null) {
2552 if (isUpdatingChildrenNow(eachParent)) return eachParent;
2553 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
2559 private boolean isLoadedInBackground(Object element) {
2560 return getLoadedInBackground(element) != null;
2563 private UpdateInfo getLoadedInBackground(Object element) {
2564 synchronized (myLoadedInBackground) {
2565 return myLoadedInBackground.get(element);
2569 private void addToLoadedInBackground(Object element, UpdateInfo info) {
2570 synchronized (myLoadedInBackground) {
2571 myLoadedInBackground.put(element, info);
2575 private void removeFromLoadedInBackground(final Object element) {
2576 synchronized (myLoadedInBackground) {
2577 myLoadedInBackground.remove(element);
2581 private boolean isLoadingInBackgroundNow() {
2582 synchronized (myLoadedInBackground) {
2583 return !myLoadedInBackground.isEmpty();
2587 private boolean queueBackgroundUpdate(final UpdateInfo updateInfo, final DefaultMutableTreeNode node) {
2588 assertIsDispatchThread();
2590 final Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor());
2592 UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor);
2593 if (loaded != null) {
2594 loaded.apply(updateInfo);
2598 addToLoadedInBackground(oldElementFromDescriptor, updateInfo);
2600 maybeSetBusyAndScheduleWaiterForReady(true, oldElementFromDescriptor);
2602 if (!isNodeBeingBuilt(node)) {
2603 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
2604 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
2607 removeFromUnbuilt(node);
2609 final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
2610 final Ref<Object> elementFromDescriptor = new Ref<Object>();
2612 final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
2614 final Runnable finalizeRunnable = new Runnable() {
2616 invokeLaterIfNeeded(new Runnable() {
2618 if (isReleased()) return;
2620 removeLoading(node, false);
2621 removeFromLoadedInBackground(elementFromDescriptor.get());
2622 removeFromLoadedInBackground(oldElementFromDescriptor);
2624 if (nodeToProcessActions[0] != null) {
2625 processNodeActionsIfReady(nodeToProcessActions[0]);
2633 Runnable buildRunnable = new Runnable() {
2635 if (updateInfo.getPass().isExpired()) {
2636 finalizeRunnable.run();
2640 if (!updateInfo.isDescriptorIsUpToDate()) {
2641 update(updateInfo.getDescriptor(), true);
2644 if (!updateInfo.isUpdateChildren()) return;
2646 Object element = getElementFromDescriptor(updateInfo.getDescriptor());
2647 if (element == null) {
2648 removeFromLoadedInBackground(oldElementFromDescriptor);
2649 finalizeRunnable.run();
2653 elementFromDescriptor.set(element);
2655 Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(updateInfo.getDescriptor()));
2657 final LoadedChildren loaded = new LoadedChildren(loadedElements);
2658 for (final Object each : loadedElements) {
2659 NodeDescriptor existingDesc = getDescriptorFrom(getNodeForElement(each, true));
2660 final NodeDescriptor eachChildDescriptor = existingDesc != null ? existingDesc : getTreeStructure().createDescriptor(each, updateInfo.getDescriptor());
2661 execute(new Runnable() {
2663 loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).getResult());
2668 children.set(loaded);
2673 public String toString() {
2674 return "runnable=" + oldElementFromDescriptor;
2678 Runnable updateRunnable = new Runnable() {
2680 if (updateInfo.getPass().isExpired()) {
2681 finalizeRunnable.run();
2685 if (children.get() == null) {
2686 finalizeRunnable.run();
2690 if (isRerunNeeded(updateInfo.getPass())) {
2691 removeFromLoadedInBackground(elementFromDescriptor.get());
2692 getUpdater().requeue(updateInfo.getPass());
2696 removeFromLoadedInBackground(elementFromDescriptor.get());
2698 if (myUnbuiltNodes.contains(node)) {
2699 Pair<Boolean, LoadedChildren> unbuilt =
2700 processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()),
2702 if (unbuilt.getFirst()) {
2703 nodeToProcessActions[0] = node;
2708 updateNodeChildren(node, updateInfo.getPass(), children.get(), true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(),
2712 if (isRerunNeeded(updateInfo.getPass())) {
2713 getUpdater().requeue(updateInfo.getPass());
2717 Object element = elementFromDescriptor.get();
2719 if (element != null) {
2720 removeLoading(node, false);
2721 nodeToProcessActions[0] = node;
2725 queueToBackground(buildRunnable, updateRunnable, node).doWhenProcessed(finalizeRunnable).doWhenRejected(new Runnable() {
2727 updateInfo.getPass().expire();
2734 private boolean isExpanded(@NotNull DefaultMutableTreeNode node, boolean isExpanded) {
2735 return isExpanded || myTree.isExpanded(getPathFor(node));
2738 private void removeLoading(DefaultMutableTreeNode parent, boolean forced) {
2739 if (!forced && myUnbuiltNodes.contains(parent) && !myCancelledBuild.containsKey(parent)) {
2743 for (int i = 0; i < parent.getChildCount(); i++) {
2744 TreeNode child = parent.getChildAt(i);
2745 if (removeIfLoading(child)) {
2750 if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
2751 insertLoadingNode(parent, false);
2757 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
2758 assertIsDispatchThread();
2760 if (isNodeBeingBuilt(node)) return;
2762 final Object o = node.getUserObject();
2763 if (!(o instanceof NodeDescriptor)) return;
2766 if (isYeildingNow()) {
2767 myPendingNodeActions.add(node);
2771 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
2773 boolean childrenReady = !isLoadedInBackground(element) && !isUpdatingChildrenNow(node);
2775 processActions(node, element, myNodeActions, childrenReady ? myNodeChildrenActions : null);
2776 if (childrenReady) {
2777 processActions(node, element, myNodeChildrenActions, null);
2780 if (!isUpdatingParent(node) && !isWorkerBusy()) {
2781 final UpdaterTreeState state = myUpdaterState;
2782 if (myNodeActions.isEmpty() && state != null && !state.isProcessingNow()) {
2783 if (canInitiateNewActivity()) {
2784 if (!state.restore(childrenReady ? node : null)) {
2785 setUpdaterState(state);
2795 private static void processActions(DefaultMutableTreeNode node,
2797 final Map<Object, List<NodeAction>> nodeActions,
2798 @Nullable final Map<Object, List<NodeAction>> secondaryNodeAction) {
2799 final List<NodeAction> actions = nodeActions.get(element);
2800 if (actions != null) {
2801 nodeActions.remove(element);
2803 List<NodeAction> secondary = secondaryNodeAction != null ? secondaryNodeAction.get(element) : null;
2804 for (NodeAction each : actions) {
2805 if (secondary != null && secondary.contains(each)) {
2806 secondary.remove(each);
2814 private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) {
2815 if (!canInitiateNewActivity()) return false;
2816 if (!getBuilder().isSmartExpand()) return false;
2818 boolean smartExpand = !myNotForSmartExpand.contains(node) && canSmartExpand;
2819 return smartExpand && validateAutoExpand(smartExpand, getElementFor(node));
2822 private void processSmartExpand(final DefaultMutableTreeNode node, final boolean canSmartExpand, boolean forced) {
2823 if (!canInitiateNewActivity()) return;
2824 if (!getBuilder().isSmartExpand()) return;
2826 boolean can = canSmartExpand(node, canSmartExpand);
2828 if (!can && !forced) return;
2830 if (isNodeBeingBuilt(node) && !forced) {
2831 addNodeAction(getElementFor(node), new NodeAction() {
2832 public void onReady(DefaultMutableTreeNode node) {
2833 processSmartExpand(node, canSmartExpand, true);
2838 TreeNode child = getChildForSmartExpand(node);
2839 if (child != null) {
2840 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child);
2841 processInnerChange(new Runnable() {
2843 myTree.expandPath(childPath);
2851 private static TreeNode getChildForSmartExpand(DefaultMutableTreeNode node) {
2852 int realChildCount = 0;
2853 TreeNode nodeToExpand = null;
2855 for (int i = 0; i < node.getChildCount(); i++) {
2856 TreeNode eachChild = node.getChildAt(i);
2858 if (!isLoadingNode(eachChild)) {
2860 if (nodeToExpand == null) {
2861 nodeToExpand = eachChild;
2865 if (realChildCount > 1) {
2866 nodeToExpand = null;
2871 return nodeToExpand;
2874 public static boolean isLoadingChildrenFor(final Object nodeObject) {
2875 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
2877 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
2879 int loadingNodes = 0;
2880 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
2881 TreeNode child = node.getChildAt(i);
2882 if (isLoadingNode(child)) {
2886 return loadingNodes > 0 && loadingNodes == node.getChildCount();
2889 public boolean isParentLoadingInBackground(Object nodeObject) {
2890 return getParentLoadingInBackground(nodeObject) != null;
2893 private DefaultMutableTreeNode getParentLoadingInBackground(Object nodeObject) {
2894 if (!(nodeObject instanceof DefaultMutableTreeNode)) return null;
2896 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
2898 TreeNode eachParent = node.getParent();
2900 while (eachParent != null) {
2901 eachParent = eachParent.getParent();
2902 if (eachParent instanceof DefaultMutableTreeNode) {
2903 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
2904 if (isLoadedInBackground(eachElement)) return (DefaultMutableTreeNode)eachParent;
2911 protected static String getLoadingNodeText() {
2912 return IdeBundle.message("progress.searching");
2915 private ActionCallback processExistingNode(final DefaultMutableTreeNode childNode,
2916 final NodeDescriptor childDescriptor,
2917 final DefaultMutableTreeNode parentNode,
2918 final MutualMap<Object, Integer> elementToIndexMap,
2919 final TreeUpdatePass pass,
2920 final boolean canSmartExpand,
2921 final boolean forceUpdate,
2922 LoadedChildren parentPreloadedChildren) {
2924 final ActionCallback result = new ActionCallback();
2926 if (pass.isExpired()) {
2927 return new ActionCallback.Rejected();
2930 final Ref<NodeDescriptor> childDesc = new Ref<NodeDescriptor>(childDescriptor);
2932 if (childDesc.get() == null) {
2934 return new ActionCallback.Rejected();
2936 final Object oldElement = getElementFromDescriptor(childDesc.get());
2937 if (oldElement == null) {
2939 return new ActionCallback.Rejected();
2942 AsyncResult<Boolean> update = new AsyncResult<Boolean>();
2943 if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) {
2944 update.setDone(parentPreloadedChildren.isUpdated(oldElement));
2947 update = update(childDesc.get(), false);
2950 update.doWhenDone(new AsyncResult.Handler<Boolean>() {
2951 public void run(Boolean isChanged) {
2952 final Ref<Boolean> changes = new Ref<Boolean>(isChanged);
2954 final Ref<Boolean> forceRemapping = new Ref<Boolean>(false);
2955 final Ref<Object> newElement = new Ref<Object>(getElementFromDescriptor(childDesc.get()));
2957 final Integer index =
2958 newElement.get() != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc.get())) : null;
2959 final AsyncResult<Boolean> updateIndexDone = new AsyncResult<Boolean>();
2960 final ActionCallback indexReady = new ActionCallback();
2961 if (index != null) {
2962 final Object elementFromMap = elementToIndexMap.getKey(index);
2963 if (elementFromMap != newElement.get() && elementFromMap.equals(newElement.get())) {
2964 if (isInStructure(elementFromMap) && isInStructure(newElement.get())) {
2965 if (parentNode.getUserObject() instanceof NodeDescriptor) {
2966 final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
2967 childDesc.set(getTreeStructure().createDescriptor(elementFromMap, parentDescriptor));
2968 NodeDescriptor oldDesc = getDescriptorFrom(childNode);
2969 if (oldDesc != null) {
2970 childDesc.get().applyFrom(oldDesc);
2973 childNode.setUserObject(childDesc.get());
2974 newElement.set(elementFromMap);
2975 forceRemapping.set(true);
2976 update(childDesc.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
2977 public void run(Boolean isChanged) {
2978 changes.set(isChanged);
2979 updateIndexDone.setDone(isChanged);
2985 updateIndexDone.setDone(changes.get());
2989 updateIndexDone.setDone(changes.get());
2992 updateIndexDone.doWhenDone(new Runnable() {
2994 if (childDesc.get().getIndex() != index.intValue()) {
2997 childDesc.get().setIndex(index.intValue());
2998 indexReady.setDone();
3003 updateIndexDone.setDone();
3006 updateIndexDone.doWhenDone(new Runnable() {
3008 //if (index != null && changes.get()) {
3009 // updateNodeImageAndPosition(childNode, false, changes.get());
3011 if (!oldElement.equals(newElement.get()) || forceRemapping.get()) {
3012 removeMapping(oldElement, childNode, newElement.get());
3013 if (newElement.get() != null) {
3014 createMapping(newElement.get(), childNode);
3016 NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
3017 if (parentDescriptor != null) {
3018 parentDescriptor.setChildrenSortingStamp(-1);
3022 if (index == null) {
3023 int selectedIndex = -1;
3024 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {