IDEADEV-41812 - New project view: File selection is lost after rename
[fedora-idea.git] / platform / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUi.java
blob434ccefcd86c5d33f04f45eac44f28c05e0b29e1
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;
113 private boolean myReleaseRequested;
115 private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
116 private long myJanitorPollPeriod = Time.SECOND * 10;
117 private boolean myCheckStructure = false;
120 private boolean myCanYield = false;
122 private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
124 private boolean myYeildingNow;
126 private final Set<DefaultMutableTreeNode> myPendingNodeActions = new HashSet<DefaultMutableTreeNode>();
127 private final Set<Runnable> myYeildingDoneRunnables = new HashSet<Runnable>();
129 private final Alarm myBusyAlarm = new Alarm();
130 private final Runnable myWaiterForReady = new Runnable() {
131 public void run() {
132 maybeSetBusyAndScheduleWaiterForReady(false);
136 private final RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
137 private final RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
138 private final RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimout");
140 private boolean myWasEverIndexNotReady;
141 private boolean myShowing;
142 private FocusAdapter myFocusListener = new FocusAdapter() {
143 @Override
144 public void focusGained(FocusEvent e) {
145 maybeReady();
148 private Set<DefaultMutableTreeNode> myNotForSmartExpand = new HashSet<DefaultMutableTreeNode>();
149 private TreePath myRequestedExpand;
150 private ActionCallback myInitialized = new ActionCallback();
151 private Map<Object, ActionCallback> myReadyCallbacks = new WeakHashMap<Object, ActionCallback>();
153 private boolean myPassthroughMode = false;
156 private Set<Object> myAutoExpandRoots = new HashSet<Object>();
157 private final RegistryValue myAutoExpandDepth = Registry.get("ide.tree.autoExpandMaxDepth");
159 private Set<DefaultMutableTreeNode> myWillBeExpaned = new HashSet<DefaultMutableTreeNode>();
161 protected void init(AbstractTreeBuilder builder,
162 JTree tree,
163 DefaultTreeModel treeModel,
164 AbstractTreeStructure treeStructure,
165 @Nullable Comparator<NodeDescriptor> comparator,
166 boolean updateIfInactive) {
167 myBuilder = builder;
168 myTree = tree;
169 myTreeModel = treeModel;
170 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
171 myTree.setModel(myTreeModel);
172 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
173 setTreeStructure(treeStructure);
174 myNodeDescriptorComparator = comparator;
175 myUpdateIfInactive = updateIfInactive;
177 myExpansionListener = new MyExpansionListener();
178 myTree.addTreeExpansionListener(myExpansionListener);
180 mySelectionListener = new MySelectionListener();
181 myTree.addTreeSelectionListener(mySelectionListener);
183 setUpdater(getBuilder().createUpdater());
184 myProgress = getBuilder().createProgressIndicator();
185 Disposer.register(getBuilder(), getUpdater());
187 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
188 public void showNotify() {
189 myShowing = true;
190 myWasEverShown = true;
191 if (!isReleaseRequested()) {
192 activate(true);
196 public void hideNotify() {
197 myShowing = false;
198 if (!validateReleaseRequested()) {
199 deactivate();
203 Disposer.register(getBuilder(), uiNotify);
205 myTree.addFocusListener(myFocusListener);
209 boolean isNodeActionsPending() {
210 return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty();
213 private void clearNodeActions() {
214 myNodeActions.clear();
215 myNodeChildrenActions.clear();
218 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
219 if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
221 if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
222 final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
223 final boolean isBusy = !isReady() || forcedBusy;
224 if (isBusy && tree.isShowing()) {
225 tree.setPaintBusy(true);
226 myBusyAlarm.cancelAllRequests();
227 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
229 else {
230 tree.setPaintBusy(false);
235 private void initClearanceServiceIfNeeded() {
236 if (ourClearanceService != null) return;
238 ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor", Thread.MIN_PRIORITY + 1);
239 ourClearanceService.scheduleWithFixedDelay(new Runnable() {
240 public void run() {
241 cleanUpAll();
243 }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
246 private void cleanUpAll() {
247 final long now = System.currentTimeMillis();
248 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
249 for (AbstractTreeUi eachUi : uis) {
250 if (eachUi == null) continue;
251 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
252 if (timeToCleanup == null) continue;
253 if (now >= timeToCleanup.longValue()) {
254 ourUi2Countdown.remove(eachUi);
255 Runnable runnable = new Runnable() {
256 public void run() {
257 getBuilder().cleanUp();
260 if (isPassthroughMode()) {
261 runnable.run();
262 } else {
263 UIUtil.invokeAndWaitIfNeeded(runnable);
269 protected void doCleanUp() {
270 Runnable cleanup = new Runnable() {
271 public void run() {
272 if (!isReleaseRequested()) {
273 cleanUpNow();
278 if (isPassthroughMode()) {
279 cleanup.run();
280 } else {
281 UIUtil.invokeLaterIfNeeded(cleanup);
285 private void disposeClearanceService() {
286 try {
287 if (ourClearanceService != null) {
288 ourClearanceService.shutdown();
289 ourClearanceService = null;
292 catch (AccessControlException e) {
293 LOG.warn(e);
297 public void activate(boolean byShowing) {
298 myCanProcessDeferredSelections = true;
299 ourUi2Countdown.remove(this);
301 if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) {
302 getBuilder().updateFromRoot();
305 getUpdater().showNotify();
307 myWasEverShown |= byShowing;
310 public void deactivate() {
311 getUpdater().hideNotify();
312 myBusyAlarm.cancelAllRequests();
314 if (!myWasEverShown) return;
316 if (isNodeActionsPending()) {
317 cancelBackgroundLoading();
318 myUpdateFromRootRequested = true;
321 if (getClearOnHideDelay() >= 0) {
322 ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
323 initClearanceServiceIfNeeded();
327 public void requestRelease() {
328 if (isReleaseRequested()) return;
330 assertIsDispatchThread();
332 myReleaseRequested = true;
334 getUpdater().requestRelease();
336 maybeReady();
339 private void releaseNow() {
340 myTree.removeTreeExpansionListener(myExpansionListener);
341 myTree.removeTreeSelectionListener(mySelectionListener);
342 myTree.removeFocusListener(myFocusListener);
344 disposeNode(getRootNode());
345 myElementToNodeMap.clear();
346 getUpdater().cancelAllRequests();
347 if (myWorker != null) {
348 myWorker.dispose(true);
349 clearWorkerTasks();
351 TREE_NODE_WRAPPER.setValue(null);
352 if (myProgress != null) {
353 myProgress.cancel();
355 disposeClearanceService();
357 myTree = null;
358 setUpdater(null);
359 myWorker = null;
360 myTreeStructure = null;
361 myBuilder.releaseUi();
362 myBuilder = null;
364 clearNodeActions();
366 myDeferredSelections.clear();
367 myDeferredExpansions.clear();
368 myYeildingDoneRunnables.clear();
371 public boolean isReleaseRequested() {
372 return myReleaseRequested;
375 public boolean validateReleaseRequested() {
376 if (isReleaseRequested()) {
377 SwingUtilities.invokeLater(new Runnable() {
378 public void run() {
379 maybeReady();
382 return true;
383 } else {
384 return false;
388 public boolean isReleased() {
389 return myBuilder == null;
392 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
393 if (!myUnbuiltNodes.contains(node)) return;
394 if (isLoadedInBackground(getElementFor(node))) return;
396 if (!isReleaseRequested()) {
397 getTreeStructure().commit();
398 addSubtreeToUpdate(node);
399 getUpdater().performUpdate();
400 } else {
401 processNodeActionsIfReady(node);
405 public final AbstractTreeStructure getTreeStructure() {
406 return myTreeStructure;
409 public final JTree getTree() {
410 return myTree;
413 @Nullable
414 private NodeDescriptor getDescriptorFrom(DefaultMutableTreeNode node) {
415 return (NodeDescriptor)node.getUserObject();
418 @Nullable
419 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
420 DefaultMutableTreeNode result = null;
421 if (validateAgainstStructure) {
422 int index = 0;
423 while (true) {
424 final DefaultMutableTreeNode node = findNode(element, index);
425 if (node == null) break;
427 if (isNodeValidForElement(element, node)) {
428 result = node;
429 break;
432 index++;
435 else {
436 result = getFirstNode(element);
440 if (result != null && !isNodeInStructure(result)) {
441 disposeNode(result);
442 result = null;
445 return result;
448 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
449 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
452 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
453 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
456 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
457 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
458 final Object parentElement = getElementFor(parent);
459 if (!isInStructure(parentElement)) return false;
461 if (parent instanceof ElementNode) {
462 return ((ElementNode)parent).isValidChild(element);
464 else {
465 for (int i = 0; i < parent.getChildCount(); i++) {
466 final TreeNode child = parent.getChildAt(i);
467 final Object eachElement = getElementFor(child);
468 if (element.equals(eachElement)) return true;
472 return false;
475 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
476 boolean valid = true;
477 while (true) {
478 if (eachParent == null) {
479 valid = eachParentNode == null;
480 break;
483 if (!eachParent.equals(getElementFor(eachParentNode))) {
484 valid = false;
485 break;
488 eachParent = getTreeStructure().getParentElement(eachParent);
489 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
491 return valid;
494 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
495 DefaultMutableTreeNode node = null;
496 for (final Object pathElement : path) {
497 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
498 if (node == null) {
499 break;
502 return node;
505 public final void buildNodeForElement(Object element) {
506 getUpdater().performUpdate();
507 DefaultMutableTreeNode node = getNodeForElement(element, false);
508 if (node == null) {
509 final java.util.List<Object> elements = new ArrayList<Object>();
510 while (true) {
511 element = getTreeStructure().getParentElement(element);
512 if (element == null) {
513 break;
515 elements.add(0, element);
518 for (final Object element1 : elements) {
519 node = getNodeForElement(element1, false);
520 if (node != null) {
521 expand(node, true);
527 public final void buildNodeForPath(Object[] path) {
528 getUpdater().performUpdate();
529 DefaultMutableTreeNode node = null;
530 for (final Object pathElement : path) {
531 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
532 if (node != null && node != path[path.length - 1]) {
533 expand(node, true);
538 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
539 myNodeDescriptorComparator = nodeDescriptorComparator;
540 myLastComparatorStamp = -1;
541 getBuilder().queueUpdateFrom(getTreeStructure().getRootElement(), true);
544 protected AbstractTreeBuilder getBuilder() {
545 return myBuilder;
548 protected final void initRootNode() {
549 if (myUpdateIfInactive) {
550 activate(false);
552 else {
553 myUpdateFromRootRequested = true;
557 private boolean initRootNodeNowIfNeeded(final TreeUpdatePass pass) {
558 boolean wasCleanedUp = false;
559 if (myRootNodeWasInitialized) {
560 Object root = getTreeStructure().getRootElement();
561 Object currentRoot = getElementFor(myRootNode);
563 if (Comparing.equal(root, currentRoot)) return false;
565 cleanUpNow();
566 wasCleanedUp = true;
569 myRootNodeWasInitialized = true;
571 final Object rootElement = getTreeStructure().getRootElement();
572 addNodeAction(rootElement, new NodeAction() {
573 public void onReady(final DefaultMutableTreeNode node) {
574 processDeferredActions();
576 }, false);
579 final Ref<NodeDescriptor> rootDescriptor = new Ref<NodeDescriptor>(null);
580 final boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(rootElement);
582 Runnable build = new Runnable() {
583 public void run() {
584 rootDescriptor.set(getTreeStructure().createDescriptor(rootElement, null));
585 getRootNode().setUserObject(rootDescriptor.get());
586 update(rootDescriptor.get(), true);
591 Runnable update = new Runnable() {
592 public void run() {
593 if (getElementFromDescriptor(rootDescriptor.get()) != null) {
594 createMapping(getElementFromDescriptor(rootDescriptor.get()), getRootNode());
598 insertLoadingNode(getRootNode(), true);
600 boolean willUpdate = false;
601 if (isAutoExpand(rootDescriptor.get())) {
602 willUpdate = myUnbuiltNodes.contains(getRootNode());
603 expand(getRootNode(), true);
605 if (!willUpdate) {
606 updateNodeChildren(getRootNode(), pass, null, false, false, false, true);
608 if (getRootNode().getChildCount() == 0) {
609 myTreeModel.nodeChanged(getRootNode());
614 if (bgLoading) {
615 queueToBackground(build, update, null);
617 else {
618 build.run();
619 update.run();
622 return wasCleanedUp;
625 private boolean isAutoExpand(NodeDescriptor descriptor) {
626 return isAutoExpand(descriptor, true);
629 private boolean isAutoExpand(NodeDescriptor descriptor, boolean validate) {
630 boolean autoExpand = false;
632 if (descriptor != null) {
633 autoExpand = getBuilder().isAutoExpandNode(descriptor);
636 Object element = getElementFromDescriptor(descriptor);
637 if (validate) {
638 autoExpand = validateAutoExpand(autoExpand, element);
641 if (!autoExpand && !myTree.isRootVisible()) {
642 if (element != null && element.equals(getTreeStructure().getRootElement())) return true;
645 return autoExpand;
648 private boolean validateAutoExpand(boolean autoExpand, Object element) {
649 if (autoExpand) {
650 int distance = getDistanceToAutoExpandRoot(element);
651 if (distance < 0) {
652 myAutoExpandRoots.add(element);
653 } else {
654 if (distance >= myAutoExpandDepth.asInteger() - 1) {
655 autoExpand = false;
659 if (autoExpand) {
660 DefaultMutableTreeNode node = getNodeForElement(element, false);
661 if (isInVisibleAutoExpandChain(node)) {
662 autoExpand = true;
663 } else {
664 autoExpand = false;
668 return autoExpand;
671 private boolean isInVisibleAutoExpandChain(DefaultMutableTreeNode child) {
672 TreeNode eachParent = child;
673 while (eachParent != null) {
675 if (myRootNode == eachParent) return true;
677 NodeDescriptor eachDescriptor = getDescriptorFrom((DefaultMutableTreeNode)eachParent);
678 if (!isAutoExpand(eachDescriptor, false)) {
679 TreePath path = getPathFor(eachParent);
680 if (myWillBeExpaned.contains(path.getLastPathComponent()) || (myTree.isExpanded(path) && myTree.isVisible(path))) {
681 return true;
682 } else {
683 return false;
686 eachParent = eachParent.getParent();
689 return false;
692 private int getDistanceToAutoExpandRoot(Object element) {
693 int distance = 0;
695 Object eachParent = element;
696 while (eachParent != null) {
697 if (myAutoExpandRoots.contains(eachParent)) break;
698 eachParent = getTreeStructure().getParentElement(eachParent);
699 distance++;
702 return eachParent != null ? distance : -1;
705 private boolean isAutoExpand(DefaultMutableTreeNode node) {
706 return isAutoExpand(getDescriptorFrom(node));
709 private AsyncResult<Boolean> update(final NodeDescriptor nodeDescriptor, boolean now) {
710 final AsyncResult<Boolean> result = new AsyncResult<Boolean>();
712 if (now || isPassthroughMode()) {
713 return new AsyncResult<Boolean>().setDone(_update(nodeDescriptor));
716 Object element = getElementFromDescriptor(nodeDescriptor);
717 boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(element);
719 boolean edt = isEdt();
720 if (bgLoading) {
721 if (edt) {
722 final Ref<Boolean> changes = new Ref<Boolean>(false);
723 queueToBackground(new Runnable() {
724 public void run() {
725 changes.set(_update(nodeDescriptor));
727 }, new Runnable() {
728 public void run() {
729 result.setDone(changes.get());
731 }, null);
733 else {
734 result.setDone(_update(nodeDescriptor));
737 else {
738 if (edt || !myWasEverShown) {
739 result.setDone(_update(nodeDescriptor));
741 else {
742 UIUtil.invokeLaterIfNeeded(new Runnable() {
743 public void run() {
744 if (!validateReleaseRequested()) {
745 result.setDone(_update(nodeDescriptor));
747 else {
748 result.setRejected();
755 result.doWhenDone(new AsyncResult.Handler<Boolean>() {
756 public void run(Boolean changes) {
757 if (changes) {
758 final long updateStamp = nodeDescriptor.getUpdateCount();
759 UIUtil.invokeLaterIfNeeded(new Runnable() {
760 public void run() {
761 Object element = nodeDescriptor.getElement();
762 DefaultMutableTreeNode node = getNodeForElement(element, false);
763 if (node != null) {
764 TreePath path = getPathFor(node);
765 if (path != null && myTree.isVisible(path)) {
766 updateNodeImageAndPosition(node, false);
776 return result;
779 private boolean _update(NodeDescriptor nodeDescriptor) {
780 nodeDescriptor.setUpdateCount(nodeDescriptor.getUpdateCount() + 1);
781 return getBuilder().updateNodeDescriptor(nodeDescriptor);
784 private void assertIsDispatchThread() {
785 if (isPassthroughMode()) return;
787 if (isTreeShowing() && !isEdt()) {
788 LOG.error("Must be in event-dispatch thread");
792 private boolean isEdt() {
793 return SwingUtilities.isEventDispatchThread();
796 private boolean isTreeShowing() {
797 return myShowing;
800 private void assertNotDispatchThread() {
801 if (isPassthroughMode()) return;
803 if (isEdt()) {
804 LOG.error("Must not be in event-dispatch thread");
808 private void processDeferredActions() {
809 processDeferredActions(myDeferredSelections);
810 processDeferredActions(myDeferredExpansions);
813 private void processDeferredActions(Set<Runnable> actions) {
814 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
815 actions.clear();
816 for (Runnable runnable : runnables) {
817 runnable.run();
821 //todo: to make real callback
822 public ActionCallback queueUpdate(Object element) {
823 AbstractTreeUpdater updater = getUpdater();
824 if (updater == null) {
825 return new ActionCallback.Rejected();
828 final ActionCallback result = new ActionCallback();
829 DefaultMutableTreeNode node = getNodeForElement(element, false);
830 if (node != null) {
831 addSubtreeToUpdate(node);
833 else {
834 addSubtreeToUpdate(getRootNode());
837 updater.runAfterUpdate(new Runnable() {
838 public void run() {
839 result.setDone();
842 return result;
845 public void doUpdateFromRoot() {
846 updateSubtree(getRootNode(), false);
849 public ActionCallback doUpdateFromRootCB() {
850 final ActionCallback cb = new ActionCallback();
851 getUpdater().runAfterUpdate(new Runnable() {
852 public void run() {
853 cb.setDone();
856 updateSubtree(getRootNode(), false);
857 return cb;
860 public final void updateSubtree(DefaultMutableTreeNode node, boolean canSmartExpand) {
861 updateSubtree(new TreeUpdatePass(node), canSmartExpand);
864 public final void updateSubtree(TreeUpdatePass pass, boolean canSmartExpand) {
865 if (getUpdater() != null) {
866 getUpdater().addSubtreeToUpdate(pass);
868 else {
869 updateSubtreeNow(pass, canSmartExpand);
873 final void updateSubtreeNow(TreeUpdatePass pass, boolean canSmartExpand) {
874 maybeSetBusyAndScheduleWaiterForReady(true);
876 boolean consumed = initRootNodeNowIfNeeded(pass);
877 if (consumed) return;
879 final DefaultMutableTreeNode node = pass.getNode();
881 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
883 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
885 boolean forceUpdate = true;
886 TreePath path = getPathFor(node);
887 boolean invisible = !myTree.isExpanded(path) && (path.getParentPath() == null || !myTree.isExpanded(path.getParentPath()));
889 if (invisible && myUnbuiltNodes.contains(node)) {
890 forceUpdate = false;
893 updateNodeChildren(node, pass, null, false, canSmartExpand, forceUpdate, false);
896 private boolean isToBuildInBackground(NodeDescriptor descriptor) {
897 return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor));
900 @NotNull
901 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
902 final UpdaterTreeState oldState = myUpdaterState;
903 if (oldState == null) {
904 myUpdaterState = state;
905 return state;
907 else {
908 oldState.addAll(state);
909 return oldState;
913 protected void doUpdateNode(final DefaultMutableTreeNode node) {
914 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
915 final NodeDescriptor descriptor = getDescriptorFrom(node);
916 final Object prevElement = getElementFromDescriptor(descriptor);
917 if (prevElement == null) return;
918 update(descriptor, false).doWhenDone(new AsyncResult.Handler<Boolean>() {
919 public void run(Boolean changes) {
920 if (!isValid(descriptor)) {
921 if (isInStructure(prevElement)) {
922 getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement));
923 return;
926 if (changes) {
927 updateNodeImageAndPosition(node, true);
933 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
934 return getBuilder().getTreeStructureElement(descriptor);
937 private void updateNodeChildren(final DefaultMutableTreeNode node,
938 final TreeUpdatePass pass,
939 @Nullable LoadedChildren loadedChildren,
940 boolean forcedNow,
941 final boolean toSmartExpand,
942 boolean forceUpdate,
943 final boolean descriptorIsUpToDate) {
944 try {
945 getTreeStructure().commit();
948 final NodeDescriptor descriptor = getDescriptorFrom(node);
949 if (descriptor == null) {
950 removeLoading(node, true);
951 return;
954 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node);
955 final boolean wasLeaf = node.getChildCount() == 0;
958 boolean bgBuild = isToBuildInBackground(descriptor);
959 boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded;
961 if (notRequiredToUpdateChildren && forceUpdate && !wasExpanded) {
962 boolean alwaysPlus = getBuilder().isAlwaysShowPlus(descriptor);
963 if (alwaysPlus && wasLeaf) {
964 notRequiredToUpdateChildren = false;
965 } else {
966 notRequiredToUpdateChildren = alwaysPlus;
970 final Ref<LoadedChildren> preloaded = new Ref<LoadedChildren>(loadedChildren);
971 boolean descriptorWasUpdated = descriptorIsUpToDate;
973 if (notRequiredToUpdateChildren) {
974 if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) {
975 insertLoadingNode(node, true);
977 return;
980 if (!forcedNow) {
981 if (!bgBuild) {
982 if (myUnbuiltNodes.contains(node)) {
983 if (!descriptorWasUpdated) {
984 update(descriptor, true);
985 descriptorWasUpdated = true;
988 if (processAlwaysLeaf(node)) return;
990 Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null);
991 if (unbuilt.getFirst()) return;
992 preloaded.set(unbuilt.getSecond());
998 final boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded);
1000 if (!forcedNow && isToBuildInBackground(descriptor)) {
1001 if (processAlwaysLeaf(node)) return;
1003 queueBackgroundUpdate(
1004 new UpdateInfo(descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorWasUpdated), node);
1005 return;
1007 else {
1008 if (!descriptorWasUpdated) {
1009 update(descriptor, false).doWhenDone(new Runnable() {
1010 public void run() {
1011 if (processAlwaysLeaf(node)) return;
1012 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
1016 else {
1017 if (processAlwaysLeaf(node)) return;
1019 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
1023 finally {
1024 processNodeActionsIfReady(node);
1028 private boolean processAlwaysLeaf(DefaultMutableTreeNode node) {
1029 Object element = getElementFor(node);
1030 NodeDescriptor desc = getDescriptorFrom(node);
1032 if (desc == null) return false;
1034 if (getTreeStructure().isAlwaysLeaf(element)) {
1035 removeLoading(node, true);
1037 if (node.getChildCount() > 0) {
1038 final TreeNode[] children = new TreeNode[node.getChildCount()];
1039 for (int i = 0; i < node.getChildCount(); i++) {
1040 children[i] = node.getChildAt(i);
1043 if (isSelectionInside(node)) {
1044 addSelectionPath(getPathFor(node), true, Condition.TRUE, null);
1047 processInnerChange(new Runnable() {
1048 public void run() {
1049 for (TreeNode each : children) {
1050 removeNodeFromParent((MutableTreeNode)each, true);
1051 disposeNode((DefaultMutableTreeNode)each);
1057 removeFromUnbuilt(node);
1058 desc.setWasDeclaredAlwaysLeaf(true);
1059 processNodeActionsIfReady(node);
1060 return true;
1061 } else {
1062 boolean wasLeaf = desc.isWasDeclaredAlwaysLeaf();
1063 desc.setWasDeclaredAlwaysLeaf(false);
1065 if (wasLeaf) {
1066 insertLoadingNode(node, true);
1069 return false;
1073 private boolean isChildNodeForceUpdate(DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) {
1074 TreePath path = getPathFor(node);
1075 return parentForceUpdate && (parentExpanded || myTree.isExpanded(path));
1078 private void updateNodeChildrenNow(final DefaultMutableTreeNode node,
1079 final TreeUpdatePass pass,
1080 final LoadedChildren preloadedChildren,
1081 final boolean toSmartExpand,
1082 final boolean wasExpanded,
1083 final boolean wasLeaf,
1084 final boolean forceUpdate) {
1085 final NodeDescriptor descriptor = getDescriptorFrom(node);
1087 final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren);
1088 final LoadedChildren loadedChildren =
1089 preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray());
1092 addToUpdating(node);
1093 pass.setCurrentNode(node);
1095 final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
1097 processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren)
1098 .doWhenDone(new Runnable() {
1099 public void run() {
1100 if (isDisposed(node)) {
1101 removeFromUpdating(node);
1102 return;
1105 removeLoading(node, false);
1107 final boolean expanded = isExpanded(node, wasExpanded);
1109 if (expanded) {
1110 myWillBeExpaned.add(node);
1111 } else {
1112 myWillBeExpaned.remove(node);
1115 collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren)
1116 .doWhenDone(new AsyncResult.Handler<ArrayList<TreeNode>>() {
1117 public void run(ArrayList<TreeNode> nodesToInsert) {
1118 insertNodesInto(nodesToInsert, node);
1119 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
1120 removeLoading(node, true);
1121 removeFromUpdating(node);
1123 if (node.getChildCount() > 0) {
1124 if (expanded ) {
1125 expand(node, canSmartExpand);
1129 final Object element = getElementFor(node);
1130 addNodeAction(element, new NodeAction() {
1131 public void onReady(final DefaultMutableTreeNode node) {
1132 removeLoading(node, false);
1134 }, false);
1136 processNodeActionsIfReady(node);
1138 }).doWhenProcessed(new Runnable() {
1139 public void run() {
1140 myWillBeExpaned.remove(node);
1141 removeFromUpdating(node);
1142 processNodeActionsIfReady(node);
1146 }).doWhenRejected(new Runnable() {
1147 public void run() {
1148 removeFromUpdating(node);
1149 processNodeActionsIfReady(node);
1154 private boolean isDisposed(DefaultMutableTreeNode node) {
1155 return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
1158 private void expand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1159 expand(new TreePath(node.getPath()), canSmartExpand);
1162 private void expand(final TreePath path, boolean canSmartExpand) {
1163 if (path == null) return;
1166 final Object last = path.getLastPathComponent();
1167 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
1168 final boolean isRoot = last == myTree.getModel().getRoot();
1169 final TreePath parent = path.getParentPath();
1170 if (isRoot && !myTree.isExpanded(path)) {
1171 if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) {
1172 insertLoadingNode((DefaultMutableTreeNode)last, false);
1174 expandPath(path, canSmartExpand);
1176 else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last))) {
1177 if (last instanceof DefaultMutableTreeNode) {
1178 processNodeActionsIfReady((DefaultMutableTreeNode)last);
1181 else {
1182 if (isLeaf && myUnbuiltNodes.contains(last)) {
1183 insertLoadingNode((DefaultMutableTreeNode)last, true);
1184 expandPath(path, canSmartExpand);
1186 else if (isLeaf && parent != null) {
1187 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
1188 if (parentNode != null) {
1189 addToUnbuilt(parentNode);
1191 expandPath(parent, canSmartExpand);
1193 else {
1194 expandPath(path, canSmartExpand);
1199 private void addToUnbuilt(DefaultMutableTreeNode node) {
1200 myUnbuiltNodes.add(node);
1203 private void removeFromUnbuilt(DefaultMutableTreeNode node) {
1204 myUnbuiltNodes.remove(node);
1207 private Pair<Boolean, LoadedChildren> processUnbuilt(final DefaultMutableTreeNode node,
1208 final NodeDescriptor descriptor,
1209 final TreeUpdatePass pass,
1210 boolean isExpanded,
1211 final LoadedChildren loadedChildren) {
1212 if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) {
1213 return new Pair<Boolean, LoadedChildren>(true, null);
1216 final Object element = getElementFor(node);
1218 final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element));
1220 boolean processed;
1222 if (children.getElements().size() == 0) {
1223 removeLoading(node, true);
1224 processed = true;
1226 else {
1227 if (isAutoExpand(node)) {
1228 addNodeAction(getElementFor(node), new NodeAction() {
1229 public void onReady(final DefaultMutableTreeNode node) {
1230 final TreePath path = new TreePath(node.getPath());
1231 if (getTree().isExpanded(path) || children.getElements().size() == 0) {
1232 removeLoading(node, false);
1234 else {
1235 maybeYeild(new ActiveRunnable() {
1236 public ActionCallback run() {
1237 expand(element, null);
1238 return new ActionCallback.Done();
1240 }, pass, node);
1243 }, false);
1245 processed = false;
1248 processNodeActionsIfReady(node);
1250 return new Pair<Boolean, LoadedChildren>(processed, children);
1253 private boolean removeIfLoading(TreeNode node) {
1254 if (isLoadingNode(node)) {
1255 moveSelectionToParentIfNeeded(node);
1256 removeNodeFromParent((MutableTreeNode)node, false);
1257 return true;
1260 return false;
1263 private void moveSelectionToParentIfNeeded(TreeNode node) {
1264 TreePath path = getPathFor(node);
1265 if (myTree.getSelectionModel().isPathSelected(path)) {
1266 TreePath parentPath = path.getParentPath();
1267 myTree.getSelectionModel().removeSelectionPath(path);
1268 if (parentPath != null) {
1269 myTree.getSelectionModel().addSelectionPath(parentPath);
1274 //todo [kirillk] temporary consistency check
1275 private Object[] getChildrenFor(final Object element) {
1276 final Object[] passOne;
1277 try {
1278 passOne = getTreeStructure().getChildElements(element);
1280 catch (IndexNotReadyException e) {
1281 if (!myWasEverIndexNotReady) {
1282 myWasEverIndexNotReady = true;
1283 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
1285 return ArrayUtil.EMPTY_OBJECT_ARRAY;
1288 if (!myCheckStructure) return passOne;
1290 final Object[] passTwo = getTreeStructure().getChildElements(element);
1292 final HashSet two = new HashSet(Arrays.asList(passTwo));
1294 if (passOne.length != passTwo.length) {
1295 LOG.error(
1296 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1297 element);
1299 else {
1300 for (Object eachInOne : passOne) {
1301 if (!two.contains(eachInOne)) {
1302 LOG.error(
1303 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1304 element);
1305 break;
1310 return passOne;
1313 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert,
1314 TreeUpdatePass pass,
1315 boolean canSmartExpand,
1316 boolean forceUpdate) {
1317 for (TreeNode aNodesToInsert : nodesToInsert) {
1318 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
1319 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
1323 private ActionCallback processExistingNodes(final DefaultMutableTreeNode node,
1324 final MutualMap<Object, Integer> elementToIndexMap,
1325 final TreeUpdatePass pass,
1326 final boolean canSmartExpand,
1327 final boolean forceUpdate,
1328 final boolean wasExpaned,
1329 final LoadedChildren preloaded) {
1331 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
1332 return maybeYeild(new ActiveRunnable() {
1333 public ActionCallback run() {
1334 if (pass.isExpired()) return new ActionCallback.Rejected();
1335 if (childNodes.size() == 0) return new ActionCallback.Done();
1338 final ActionCallback result = new ActionCallback(childNodes.size());
1340 for (TreeNode each : childNodes) {
1341 final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each;
1342 if (isLoadingNode(eachChild)) {
1343 result.setDone();
1344 continue;
1347 final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned);
1349 maybeYeild(new ActiveRunnable() {
1350 @Override
1351 public ActionCallback run() {
1352 return processExistingNode(eachChild, getDescriptorFrom(eachChild), node, elementToIndexMap, pass, canSmartExpand,
1353 childForceUpdate, preloaded);
1355 }, pass, node).notify(result);
1357 if (result.isRejected()) {
1358 break;
1362 return result;
1364 }, pass, node);
1367 private boolean isRerunNeeded(TreeUpdatePass pass) {
1368 if (pass.isExpired()) return false;
1370 final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
1372 return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
1375 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
1376 final ActionCallback result = new ActionCallback();
1378 if (isRerunNeeded(pass)) {
1379 getUpdater().addSubtreeToUpdate(pass);
1380 result.setRejected();
1382 else {
1383 if (isToYieldUpdateFor(node)) {
1384 pass.setCurrentNode(node);
1385 boolean wasRun = yieldAndRun(new Runnable() {
1386 public void run() {
1387 if (validateReleaseRequested()) {
1388 result.setRejected();
1389 return;
1392 if (pass.isExpired()) {
1393 result.setRejected();
1394 return;
1397 if (isRerunNeeded(pass)) {
1398 runDone(new Runnable() {
1399 public void run() {
1400 if (!pass.isExpired()) {
1401 getUpdater().addSubtreeToUpdate(pass);
1405 result.setRejected();
1407 else {
1408 processRunnable.run().notify(result);
1411 }, pass);
1412 if (!wasRun) {
1413 result.setRejected();
1416 else {
1417 processRunnable.run().notify(result);
1421 return result;
1424 private boolean yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
1425 if (validateReleaseRequested()) return false;
1427 myYeildingPasses.add(pass);
1428 myYeildingNow = true;
1429 yield(new Runnable() {
1430 public void run() {
1431 runOnYieldingDone(new Runnable() {
1432 public void run() {
1433 executeYieldingRequest(runnable, pass);
1439 return true;
1442 public boolean isYeildingNow() {
1443 return myYeildingNow;
1446 private boolean hasSheduledUpdates() {
1447 return getUpdater().hasNodesToUpdate() || isLoadingInBackgroundNow();
1450 public boolean isReady() {
1451 return isIdle() && !hasPendingWork() && !isNodeActionsPending();
1454 public boolean hasPendingWork() {
1455 return hasNodesToUpdate() || (myUpdaterState != null && myUpdaterState.isProcessingNow());
1458 public boolean isIdle() {
1459 return !isYeildingNow() && !isWorkerBusy() && (!hasSheduledUpdates() || getUpdater().isInPostponeMode());
1462 private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
1463 try {
1464 myYeildingPasses.remove(pass);
1465 runnable.run();
1467 finally {
1468 maybeYeildingFinished();
1472 private void maybeYeildingFinished() {
1473 if (myYeildingPasses.size() == 0) {
1474 myYeildingNow = false;
1475 flushPendingNodeActions();
1479 void maybeReady() {
1480 if (isReleased()) return;
1482 if (isReady()) {
1483 if (isReleaseRequested()) {
1484 releaseNow();
1485 return;
1488 if (myTree.isShowing() || myUpdateIfInactive) {
1489 myInitialized.setDone();
1493 if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) {
1494 UpdaterTreeState oldState = myUpdaterState;
1495 if (!myUpdaterState.restore(null)) {
1496 setUpdaterState(oldState);
1499 if (!isReady()) {
1500 return;
1504 if (myTree.isShowing()) {
1505 if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
1506 TreeUtil.ensureSelection(myTree);
1510 if (myInitialized.isDone()) {
1511 for (ActionCallback each : getReadyCallbacks(true)) {
1512 each.setDone();
1518 private void flushPendingNodeActions() {
1519 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
1520 myPendingNodeActions.clear();
1522 for (DefaultMutableTreeNode each : nodes) {
1523 processNodeActionsIfReady(each);
1526 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
1527 for (Runnable each : actions) {
1528 if (!isYeildingNow()) {
1529 myYeildingDoneRunnables.remove(each);
1530 each.run();
1534 maybeReady();
1537 protected void runOnYieldingDone(Runnable onDone) {
1538 getBuilder().runOnYeildingDone(onDone);
1541 protected void yield(Runnable runnable) {
1542 getBuilder().yield(runnable);
1545 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
1546 if (!canYield()) return false;
1547 return getBuilder().isToYieldUpdateFor(node);
1550 private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor,
1551 @Nullable LoadedChildren preloadedChildren) {
1552 MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
1553 List children = preloadedChildren != null
1554 ? preloadedChildren.getElements()
1555 : Arrays.asList(getChildrenFor(getBuilder().getTreeStructureElement(descriptor)));
1556 int index = 0;
1557 for (Object child : children) {
1558 if (!isValid(child)) continue;
1559 elementToIndexMap.put(child, Integer.valueOf(index));
1560 index++;
1562 return elementToIndexMap;
1565 private void expand(final DefaultMutableTreeNode node,
1566 final NodeDescriptor descriptor,
1567 final boolean wasLeaf,
1568 final boolean canSmartExpand) {
1569 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1570 alarm.addRequest(new Runnable() {
1571 public void run() {
1572 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1574 }, WAIT_CURSOR_DELAY);
1576 if (wasLeaf && isAutoExpand(descriptor)) {
1577 expand(node, canSmartExpand);
1580 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1581 for (TreeNode node1 : nodes) {
1582 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1583 if (isLoadingNode(childNode)) continue;
1584 NodeDescriptor childDescr = getDescriptorFrom(childNode);
1585 if (isAutoExpand(childDescr)) {
1586 addNodeAction(getElementFor(childNode), new NodeAction() {
1587 public void onReady(DefaultMutableTreeNode node) {
1588 expand(childNode, canSmartExpand);
1590 }, false);
1591 addSubtreeToUpdate(childNode);
1595 int n = alarm.cancelAllRequests();
1596 if (n == 0) {
1597 myTree.setCursor(Cursor.getDefaultCursor());
1601 public static boolean isLoadingNode(final Object node) {
1602 return node instanceof LoadingNode;
1605 private AsyncResult<ArrayList<TreeNode>> collectNodesToInsert(final NodeDescriptor descriptor,
1606 final MutualMap<Object, Integer> elementToIndexMap,
1607 final DefaultMutableTreeNode parent,
1608 final boolean addLoadingNode,
1609 @NotNull final LoadedChildren loadedChildren) {
1610 final AsyncResult<ArrayList<TreeNode>> result = new AsyncResult<ArrayList<TreeNode>>();
1612 final ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1613 final Collection<Object> allElements = elementToIndexMap.getKeys();
1615 final ActionCallback processingDone = new ActionCallback(allElements.size());
1617 for (final Object child : allElements) {
1618 Integer index = elementToIndexMap.getValue(child);
1619 final Ref<NodeDescriptor> childDescr = new Ref<NodeDescriptor>(loadedChildren.getDescriptor(child));
1620 boolean needToUpdate = false;
1621 if (childDescr.get() == null) {
1622 childDescr.set(getTreeStructure().createDescriptor(child, descriptor));
1623 needToUpdate = true;
1626 //noinspection ConstantConditions
1627 if (childDescr.get() == null) {
1628 LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1629 processingDone.setDone();
1630 continue;
1632 childDescr.get().setIndex(index.intValue());
1634 final ActionCallback update = new ActionCallback();
1635 if (needToUpdate) {
1636 update(childDescr.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1637 public void run(Boolean changes) {
1638 loadedChildren.putDescriptor(child, childDescr.get(), changes);
1639 update.setDone();
1643 else {
1644 update.setDone();
1647 update.doWhenDone(new Runnable() {
1648 public void run() {
1649 Object element = getElementFromDescriptor(childDescr.get());
1650 if (element == null) {
1651 processingDone.setDone();
1653 else {
1654 DefaultMutableTreeNode node = getNodeForElement(element, false);
1655 if (node == null || node.getParent() != parent) {
1656 final DefaultMutableTreeNode childNode = createChildNode(childDescr.get());
1657 if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr.get())) {
1658 insertLoadingNode(childNode, true);
1660 else {
1661 addToUnbuilt(childNode);
1663 nodesToInsert.add(childNode);
1664 createMapping(element, childNode);
1666 processingDone.setDone();
1672 processingDone.doWhenDone(new Runnable() {
1673 public void run() {
1674 result.setDone(nodesToInsert);
1678 return result;
1681 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1682 return new ElementNode(this, descriptor);
1685 protected boolean canYield() {
1686 return myCanYield && myYeildingUpdate.asBoolean();
1689 public long getClearOnHideDelay() {
1690 return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
1693 public ActionCallback getInitialized() {
1694 return myInitialized;
1697 public ActionCallback getReady(Object requestor) {
1698 if (isReady()) {
1699 return new ActionCallback.Done();
1701 else {
1702 return addReadyCallback(requestor);
1706 private void addToUpdating(DefaultMutableTreeNode node) {
1707 synchronized (myUpdatingChildren) {
1708 myUpdatingChildren.add(node);
1712 private void removeFromUpdating(DefaultMutableTreeNode node) {
1713 synchronized (myUpdatingChildren) {
1714 myUpdatingChildren.remove(node);
1718 public boolean isUpdatingNow(DefaultMutableTreeNode node) {
1719 synchronized (myUpdatingChildren) {
1720 return myUpdatingChildren.contains(node);
1724 boolean hasUpdatingNow() {
1725 synchronized (myUpdatingChildren) {
1726 return myUpdatingChildren.size() > 0;
1730 public Map getNodeActions() {
1731 return myNodeActions;
1734 public List<Object> getLoadedChildrenFor(Object element) {
1735 List<Object> result = new ArrayList<Object>();
1737 DefaultMutableTreeNode node = (DefaultMutableTreeNode)getNodeForElement(element, false);
1738 if (node != null) {
1739 for (int i = 0; i < node.getChildCount(); i++) {
1740 TreeNode each = node.getChildAt(i);
1741 if (isLoadingNode(each)) continue;
1743 result.add(getElementFor(each));
1747 return result;
1750 public boolean hasNodesToUpdate() {
1751 return getUpdater().hasNodesToUpdate() || hasUpdatingNow() || isLoadingInBackgroundNow();
1754 public List<Object> getExpandedElements() {
1755 List<Object> result = new ArrayList<Object>();
1756 Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode()));
1757 while (enumeration.hasMoreElements()) {
1758 TreePath each = enumeration.nextElement();
1759 Object eachElement = getElementFor(each.getLastPathComponent());
1760 if (eachElement != null) {
1761 result.add(eachElement);
1765 return result;
1768 static class ElementNode extends DefaultMutableTreeNode {
1770 Set<Object> myElements = new HashSet<Object>();
1771 AbstractTreeUi myUi;
1773 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1774 super(descriptor);
1775 myUi = ui;
1778 @Override
1779 public void insert(final MutableTreeNode newChild, final int childIndex) {
1780 super.insert(newChild, childIndex);
1781 final Object element = myUi.getElementFor(newChild);
1782 if (element != null) {
1783 myElements.add(element);
1787 @Override
1788 public void remove(final int childIndex) {
1789 final TreeNode node = getChildAt(childIndex);
1790 super.remove(childIndex);
1791 final Object element = myUi.getElementFor(node);
1792 if (element != null) {
1793 myElements.remove(element);
1797 boolean isValidChild(Object childElement) {
1798 return myElements.contains(childElement);
1801 @Override
1802 public String toString() {
1803 return String.valueOf(getUserObject());
1807 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1808 return getUpdatingParent(kid) != null;
1811 private DefaultMutableTreeNode getUpdatingParent(DefaultMutableTreeNode kid) {
1812 DefaultMutableTreeNode eachParent = kid;
1813 while (eachParent != null) {
1814 if (isUpdatingNow(eachParent)) return eachParent;
1815 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1818 return null;
1821 private boolean isLoadedInBackground(Object element) {
1822 return getLoadedInBackground(element) != null;
1825 private UpdateInfo getLoadedInBackground(Object element) {
1826 synchronized (myLoadedInBackground) {
1827 return myLoadedInBackground.get(element);
1831 private void addToLoadedInBackground(Object element, UpdateInfo info) {
1832 synchronized (myLoadedInBackground) {
1833 myLoadedInBackground.put(element, info);
1837 private void removeFromLoadedInBackground(final Object element) {
1838 synchronized (myLoadedInBackground) {
1839 myLoadedInBackground.remove(element);
1843 private boolean isLoadingInBackgroundNow() {
1844 synchronized (myLoadedInBackground) {
1845 return myLoadedInBackground.size() > 0;
1849 private boolean queueBackgroundUpdate(final UpdateInfo updateInfo, final DefaultMutableTreeNode node) {
1850 assertIsDispatchThread();
1852 if (validateReleaseRequested()) return false;
1854 final Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor());
1856 UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor);
1857 if (loaded != null) {
1858 loaded.apply(updateInfo);
1859 return false;
1862 addToLoadedInBackground(oldElementFromDescriptor, updateInfo);
1864 if (!isNodeBeingBuilt(node)) {
1865 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1866 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1869 final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
1870 final Ref<Object> elementFromDescriptor = new Ref<Object>();
1871 Runnable buildRunnable = new Runnable() {
1872 public void run() {
1873 if (!updateInfo.isDescriptorIsUpToDate()) {
1874 update(updateInfo.getDescriptor(), true);
1877 Object element = getElementFromDescriptor(updateInfo.getDescriptor());
1878 if (element == null) {
1879 removeFromLoadedInBackground(oldElementFromDescriptor);
1880 return;
1883 elementFromDescriptor.set(element);
1885 Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(updateInfo.getDescriptor()));
1886 LoadedChildren loaded = new LoadedChildren(loadedElements);
1887 for (Object each : loadedElements) {
1888 NodeDescriptor eachChildDescriptor = getTreeStructure().createDescriptor(each, updateInfo.getDescriptor());
1889 loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).getResult());
1892 children.set(loaded);
1896 final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
1897 Runnable updateRunnable = new Runnable() {
1898 public void run() {
1899 if (children.get() == null) return;
1901 if (isRerunNeeded(updateInfo.getPass())) {
1902 removeFromLoadedInBackground(elementFromDescriptor.get());
1903 getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1904 return;
1907 removeFromLoadedInBackground(elementFromDescriptor.get());
1909 if (myUnbuiltNodes.contains(node)) {
1910 Pair<Boolean, LoadedChildren> unbuilt =
1911 processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()),
1912 children.get());
1913 if (unbuilt.getFirst()) {
1914 nodeToProcessActions[0] = node;
1915 return;
1919 updateNodeChildren(node, updateInfo.getPass(), children.get(), true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(),
1920 true);
1923 if (isRerunNeeded(updateInfo.getPass())) {
1924 getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1925 return;
1928 Object element = elementFromDescriptor.get();
1930 if (element != null) {
1931 removeLoading(node, true);
1932 nodeToProcessActions[0] = node;
1936 queueToBackground(buildRunnable, updateRunnable, new Runnable() {
1937 public void run() {
1938 if (nodeToProcessActions[0] != null) {
1939 processNodeActionsIfReady(nodeToProcessActions[0]);
1943 return true;
1946 private boolean isExpanded(DefaultMutableTreeNode node, boolean isExpanded) {
1947 return isExpanded || myTree.isExpanded(getPathFor(node));
1950 private void removeLoading(DefaultMutableTreeNode parent, boolean removeFromUnbuilt) {
1951 for (int i = 0; i < parent.getChildCount(); i++) {
1952 TreeNode child = parent.getChildAt(i);
1953 if (removeIfLoading(child)) {
1954 i--;
1958 if (removeFromUnbuilt) {
1959 removeFromUnbuilt(parent);
1962 if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
1963 insertLoadingNode(parent, false);
1966 maybeReady();
1969 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1970 assertIsDispatchThread();
1972 if (isNodeBeingBuilt(node)) return;
1974 final Object o = node.getUserObject();
1975 if (!(o instanceof NodeDescriptor)) return;
1978 if (isYeildingNow()) {
1979 myPendingNodeActions.add(node);
1980 return;
1983 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1985 boolean childrenReady = !isLoadedInBackground(element);
1987 processActions(node, element, myNodeActions, childrenReady ? myNodeChildrenActions : null);
1988 if (childrenReady) {
1989 processActions(node, element, myNodeChildrenActions, null);
1992 if (!isUpdatingParent(node) && !isWorkerBusy()) {
1993 final UpdaterTreeState state = myUpdaterState;
1994 if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1995 if (!state.restore(childrenReady ? node : null)) {
1996 setUpdaterState(state);
2001 maybeReady();
2005 private void processActions(DefaultMutableTreeNode node, Object element, final Map<Object, List<NodeAction>> nodeActions, @Nullable final Map<Object, List<NodeAction>> secondaryNodeAction) {
2006 final List<NodeAction> actions = nodeActions.get(element);
2007 if (actions != null) {
2008 nodeActions.remove(element);
2010 List<NodeAction> secondary = secondaryNodeAction != null ? secondaryNodeAction.get(element) : null;
2011 for (NodeAction each : actions) {
2012 if (secondary != null && secondary.contains(each)) {
2013 secondary.remove(each);
2015 each.onReady(node);
2021 private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) {
2022 if (!getBuilder().isSmartExpand()) return false;
2024 boolean smartExpand = !myNotForSmartExpand.contains(node) && canSmartExpand;
2025 return smartExpand ? validateAutoExpand(smartExpand, getElementFor(node)) : false;
2028 private void processSmartExpand(final DefaultMutableTreeNode node, final boolean canSmartExpand, boolean forced) {
2029 if (!getBuilder().isSmartExpand()) return;
2031 boolean can = canSmartExpand(node, canSmartExpand);
2033 if (!can && !forced) return;
2035 if (isNodeBeingBuilt(node) && !forced) {
2036 addNodeAction(getElementFor(node), new NodeAction() {
2037 public void onReady(DefaultMutableTreeNode node) {
2038 processSmartExpand(node, canSmartExpand, true);
2040 }, true);
2042 else {
2043 TreeNode child = getChildForSmartExpand(node);
2044 if (child != null) {
2045 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child);
2046 processInnerChange(new Runnable() {
2047 public void run() {
2048 myTree.expandPath(childPath);
2055 @Nullable
2056 private TreeNode getChildForSmartExpand(DefaultMutableTreeNode node) {
2057 int realChildCount = 0;
2058 TreeNode nodeToExpand = null;
2060 for (int i = 0; i < node.getChildCount(); i++) {
2061 TreeNode eachChild = node.getChildAt(i);
2063 if (!isLoadingNode(eachChild)) {
2064 realChildCount++;
2065 if (nodeToExpand == null) {
2066 nodeToExpand = eachChild;
2070 if (realChildCount > 1) {
2071 nodeToExpand = null;
2072 break;
2076 return nodeToExpand;
2079 public boolean isLoadingChildrenFor(final Object nodeObject) {
2080 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
2082 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
2084 int loadingNodes = 0;
2085 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
2086 TreeNode child = node.getChildAt(i);
2087 if (isLoadingNode(child)) {
2088 loadingNodes++;
2091 return loadingNodes > 0 && loadingNodes == node.getChildCount();
2094 private boolean isParentLoading(Object nodeObject) {
2095 return getParentLoading(nodeObject) != null;
2098 private DefaultMutableTreeNode getParentLoading(Object nodeObject) {
2099 if (!(nodeObject instanceof DefaultMutableTreeNode)) return null;
2101 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
2103 TreeNode eachParent = node.getParent();
2105 while (eachParent != null) {
2106 eachParent = eachParent.getParent();
2107 if (eachParent instanceof DefaultMutableTreeNode) {
2108 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
2109 if (isLoadedInBackground(eachElement)) return (DefaultMutableTreeNode)eachParent;
2113 return null;
2116 protected String getLoadingNodeText() {
2117 return IdeBundle.message("progress.searching");
2120 private ActionCallback processExistingNode(final DefaultMutableTreeNode childNode,
2121 final NodeDescriptor childDescriptor,
2122 final DefaultMutableTreeNode parentNode,
2123 final MutualMap<Object, Integer> elementToIndexMap,
2124 final TreeUpdatePass pass,
2125 final boolean canSmartExpand,
2126 final boolean forceUpdate,
2127 LoadedChildren parentPreloadedChildren) {
2129 final ActionCallback result = new ActionCallback();
2131 if (pass.isExpired()) {
2132 return new ActionCallback.Rejected();
2135 final Ref<NodeDescriptor> childDesc = new Ref<NodeDescriptor>(childDescriptor);
2137 if (childDesc.get() == null) {
2138 pass.expire();
2139 return new ActionCallback.Rejected();
2141 final Object oldElement = getElementFromDescriptor(childDesc.get());
2142 if (oldElement == null) {
2143 pass.expire();
2144 return new ActionCallback.Rejected();
2147 AsyncResult<Boolean> update = new AsyncResult<Boolean>();
2148 if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) {
2149 update.setDone(parentPreloadedChildren.isUpdated(oldElement));
2151 else {
2152 update = update(childDesc.get(), false);
2155 update.doWhenDone(new AsyncResult.Handler<Boolean>() {
2156 public void run(Boolean isChanged) {
2157 final Ref<Boolean> changes = new Ref<Boolean>(isChanged);
2159 final Ref<Boolean> forceRemapping = new Ref<Boolean>(false);
2160 final Ref<Object> newElement = new Ref<Object>(getElementFromDescriptor(childDesc.get()));
2162 final Integer index = newElement.get() != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc.get())) : null;
2163 final AsyncResult<Boolean> updateIndexDone = new AsyncResult<Boolean>();
2164 final ActionCallback indexReady = new ActionCallback();
2165 if (index != null) {
2166 final Object elementFromMap = elementToIndexMap.getKey(index);
2167 if (elementFromMap != newElement.get() && elementFromMap.equals(newElement.get())) {
2168 if (isInStructure(elementFromMap) && isInStructure(newElement.get())) {
2169 if (parentNode.getUserObject() instanceof NodeDescriptor) {
2170 final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
2171 childDesc.set(getTreeStructure().createDescriptor(elementFromMap, parentDescriptor));
2172 childNode.setUserObject(childDesc.get());
2173 newElement.set(elementFromMap);
2174 forceRemapping.set(true);
2175 update(childDesc.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
2176 public void run(Boolean isChanged) {
2177 changes.set(isChanged);
2178 updateIndexDone.setDone(isChanged);
2183 else {
2184 updateIndexDone.setDone(changes.get());
2186 } else {
2187 updateIndexDone.setDone(changes.get());
2190 updateIndexDone.doWhenDone(new Runnable() {
2191 public void run() {
2192 if (childDesc.get().getIndex() != index.intValue()) {
2193 changes.set(true);
2195 childDesc.get().setIndex(index.intValue());
2196 indexReady.setDone();
2200 else {
2201 updateIndexDone.setDone();
2204 updateIndexDone.doWhenDone(new Runnable() {
2205 public void run() {
2206 if (index != null && changes.get()) {
2207 updateNodeImageAndPosition(childNode, false);
2209 if (!oldElement.equals(newElement.get()) | forceRemapping.get()) {
2210 removeMapping(oldElement, childNode, newElement.get());
2211 if (newElement.get() != null) {
2212 createMapping(newElement.get(), childNode);
2214 getDescriptorFrom(parentNode).setChildrenSortingStamp(-1);
2217 if (index == null) {
2218 int selectedIndex = -1;
2219 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
2220 selectedIndex = parentNode.getIndex(childNode);
2223 if (childNode.getParent() instanceof DefaultMutableTreeNode) {
2224 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
2225 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
2226 if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
2227 insertLoadingNode(parent, false);
2232 Object disposedElement = getElementFor(childNode);
2234 removeNodeFromParent(childNode, selectedIndex >= 0);
2235 disposeNode(childNode);
2237 adjustSelectionOnChildRemove(parentNode, selectedIndex, disposedElement);
2239 else {
2240 elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc.get()));
2241 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
2244 if (parentNode.equals(getRootNode())) {
2245 myTreeModel.nodeChanged(getRootNode());
2248 result.setDone();
2255 return result;
2258 private void adjustSelectionOnChildRemove(DefaultMutableTreeNode parentNode, int selectedIndex, Object disposedElement) {
2259 DefaultMutableTreeNode node = getNodeForElement(disposedElement, false);
2260 if (node != null && isValidForSelectionAdjusting(node)) {
2261 Object newElement = getElementFor(node);
2262 addSelectionPath(getPathFor(node), true, getExpiredElementCondition(newElement), disposedElement);
2263 return;
2267 if (selectedIndex >= 0) {
2268 if (parentNode.getChildCount() > 0) {
2269 if (parentNode.getChildCount() > selectedIndex) {
2270 TreeNode newChildNode = parentNode.getChildAt(selectedIndex);
2271 if (isValidForSelectionAdjusting(newChildNode)) {
2272 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement), disposedElement);
2275 else {
2276 TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1);
2277 if (isValidForSelectionAdjusting(newChild)) {
2278 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement), disposedElement);
2282 else {
2283 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement), disposedElement);
2288 private boolean isValidForSelectionAdjusting(TreeNode node) {
2289 if (!myTree.isRootVisible() && getRootNode() == node) return false;
2291 if (isLoadingNode(node)) return true;
2293 final Object elementInTree = getElementFor(node);
2294 if (elementInTree == null) return false;
2296 final TreeNode parentNode = node.getParent();
2297 final Object parentElementInTree = getElementFor(parentNode);
2298 if (parentElementInTree == null) return false;
2300 final Object parentElement = getTreeStructure().getParentElement(elementInTree);
2302 return parentElementInTree.equals(parentElement);
2305 public Condition getExpiredElementCondition(final Object element) {
2306 return new Condition() {
2307 public boolean value(final Object o) {
2308 return isInStructure(element);
2313 private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement, @Nullable final Object adjustmentCause) {
2314 processInnerChange(new Runnable() {
2315 public void run() {
2316 TreePath toSelect = null;
2318 if (isLoadingNode(path.getLastPathComponent())) {
2319 final TreePath parentPath = path.getParentPath();
2320 if (parentPath != null) {
2321 if (isValidForSelectionAdjusting((TreeNode)parentPath.getLastPathComponent())) {
2322 toSelect = parentPath;
2324 else {
2325 toSelect = null;
2329 else {
2330 toSelect = path;
2333 if (toSelect != null) {
2334 myTree.addSelectionPath(toSelect);
2336 if (isAdjustedSelection && myUpdaterState != null) {
2337 final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
2338 myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement, adjustmentCause);
2345 private static TreePath getPathFor(TreeNode node) {
2346 if (node instanceof DefaultMutableTreeNode) {
2347 return new TreePath(((DefaultMutableTreeNode)node).getPath());
2349 else {
2350 ArrayList nodes = new ArrayList();
2351 TreeNode eachParent = node;
2352 while (eachParent != null) {
2353 nodes.add(eachParent);
2354 eachParent = eachParent.getParent();
2357 return new TreePath(ArrayUtil.toObjectArray(nodes));
2362 private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
2363 processInnerChange(new Runnable() {
2364 public void run() {
2365 if (willAdjustSelection) {
2366 final TreePath path = getPathFor(node);
2367 if (myTree.isPathSelected(path)) {
2368 myTree.removeSelectionPath(path);
2372 myTreeModel.removeNodeFromParent(node);
2377 private void expandPath(final TreePath path, final boolean canSmartExpand) {
2378 processInnerChange(new Runnable() {
2379 public void run() {
2380 if (path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2381 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2382 if (node.getChildCount() > 0 && !myTree.isExpanded(path)) {
2383 if (!canSmartExpand) {
2384 myNotForSmartExpand.add(node);
2386 try {
2387 myRequestedExpand = path;
2388 myTree.expandPath(path);
2389 processSmartExpand(node, canSmartExpand, false);
2391 finally {
2392 myNotForSmartExpand.remove(node);
2393 myRequestedExpand = null;
2396 else {
2397 processNodeActionsIfReady(node);
2404 private void processInnerChange(Runnable runnable) {
2405 if (myUpdaterState == null) {
2406 setUpdaterState(new UpdaterTreeState(this));
2409 myUpdaterState.process(runnable);
2412 private boolean isInnerChange() {
2413 return myUpdaterState != null && myUpdaterState.isProcessingNow();
2416 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
2417 return descriptor.update();
2420 private void makeLoadingOrLeafIfNoChildren(final DefaultMutableTreeNode node) {
2421 TreePath path = getPathFor(node);
2422 if (path == null) return;
2424 insertLoadingNode(node, true);
2426 final NodeDescriptor descriptor = getDescriptorFrom(node);
2427 if (descriptor == null) return;
2429 descriptor.setChildrenSortingStamp(-1);
2431 if (getBuilder().isAlwaysShowPlus(descriptor)) return;
2434 TreePath parentPath = path.getParentPath();
2435 if (myTree.isVisible(path) || (parentPath != null && myTree.isExpanded(parentPath))) {
2436 if (myTree.isExpanded(path)) {
2437 addSubtreeToUpdate(node);
2439 else {
2440 insertLoadingNode(node, false);
2446 private boolean isValid(DefaultMutableTreeNode node) {
2447 if (node == null) return false;
2448 final Object object = node.getUserObject();
2449 if (object instanceof NodeDescriptor) {
2450 return isValid((NodeDescriptor)object);
2453 return false;
2456 private boolean isValid(NodeDescriptor descriptor) {
2457 if (descriptor == null) return false;
2458 return isValid(getElementFromDescriptor(descriptor));
2461 private boolean isValid(Object element) {
2462 if (element instanceof ValidateableNode) {
2463 if (!((ValidateableNode)element).isValid()) return false;
2465 return getBuilder().validateNode(element);
2468 private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
2469 if (!isLoadingChildrenFor(node)) {
2470 myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
2473 if (addToUnbuilt) {
2474 addToUnbuilt(node);
2479 protected void queueToBackground(@NotNull final Runnable bgBuildAction,
2480 @Nullable final Runnable edtPostRunnable,
2481 @Nullable final Runnable finalizeEdtRunnable) {
2482 if (validateReleaseRequested()) return;
2484 registerWorkerTask(bgBuildAction);
2486 final Runnable pooledThreadWithProgressRunnable = new Runnable() {
2487 public void run() {
2488 final AbstractTreeBuilder builder = getBuilder();
2490 builder.runBackgroundLoading(new Runnable() {
2491 public void run() {
2492 assertNotDispatchThread();
2494 try {
2495 bgBuildAction.run();
2497 if (edtPostRunnable != null) {
2498 builder.updateAfterLoadedInBackground(new Runnable() {
2499 public void run() {
2500 try {
2501 assertIsDispatchThread();
2503 edtPostRunnable.run();
2505 finally {
2506 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2511 else {
2512 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2515 catch (ProcessCanceledException e) {
2516 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2518 catch (Throwable t) {
2519 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2520 throw new RuntimeException(t);
2527 Runnable pooledThreadRunnable = new Runnable() {
2528 public void run() {
2529 try {
2530 if (myProgress != null) {
2531 ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress);
2533 else {
2534 pooledThreadWithProgressRunnable.run();
2537 catch (ProcessCanceledException e) {
2538 //ignore
2543 if (isPassthroughMode()) {
2545 } else {
2546 if (myWorker == null || myWorker.isDisposed()) {
2547 myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
2548 myWorker.start();
2549 myWorker.addTaskFirst(pooledThreadRunnable);
2550 myWorker.dispose(false);
2552 else {
2553 myWorker.addTaskFirst(pooledThreadRunnable);
2558 private void registerWorkerTask(Runnable runnable) {
2559 synchronized (myActiveWorkerTasks) {
2560 myActiveWorkerTasks.add(runnable);
2564 private void unregisterWorkerTask(Runnable runnable, @Nullable Runnable finalizeRunnable) {
2565 boolean wasRemoved;
2566 synchronized (myActiveWorkerTasks) {
2567 wasRemoved = myActiveWorkerTasks.remove(runnable);
2570 if (wasRemoved && finalizeRunnable != null) {
2571 finalizeRunnable.run();
2574 maybeReady();
2577 public boolean isWorkerBusy() {
2578 synchronized (myActiveWorkerTasks) {
2579 return myActiveWorkerTasks.size() > 0;
2583 private void clearWorkerTasks() {
2584 synchronized (myActiveWorkerTasks) {
2585 myActiveWorkerTasks.clear();
2589 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node, boolean updatePosition) {
2590 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2591 NodeDescriptor descriptor = getDescriptorFrom(node);
2592 if (getElementFromDescriptor(descriptor) == null) return;
2594 boolean notified = false;
2595 if (updatePosition) {
2596 DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
2597 if (parentNode != null) {
2598 int oldIndex = parentNode.getIndex(node);
2599 int newIndex = oldIndex;
2600 if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
2601 final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
2602 for (int i = 0; i < parentNode.getChildCount(); i++) {
2603 children.add(parentNode.getChildAt(i));
2605 sortChildren(node, children, true, false);
2606 newIndex = children.indexOf(node);
2609 if (oldIndex != newIndex) {
2610 List<Object> pathsToExpand = new ArrayList<Object>();
2611 List<Object> selectionPaths = new ArrayList<Object>();
2612 TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
2613 removeNodeFromParent(node, false);
2614 myTreeModel.insertNodeInto(node, parentNode, newIndex);
2615 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
2616 notified = true;
2618 else {
2619 myTreeModel.nodeChanged(node);
2620 notified = true;
2623 else {
2624 myTreeModel.nodeChanged(node);
2625 notified = true;
2629 if (!notified) {
2630 myTreeModel.nodeChanged(node);
2635 public DefaultTreeModel getTreeModel() {
2636 return myTreeModel;
2639 private void insertNodesInto(final ArrayList<TreeNode> toInsert, final DefaultMutableTreeNode parentNode) {
2640 sortChildren(parentNode, toInsert, false, true);
2641 final ArrayList<TreeNode> all = new ArrayList<TreeNode>(toInsert.size() + parentNode.getChildCount());
2642 all.addAll(toInsert);
2643 all.addAll(TreeUtil.childrenToArray(parentNode));
2645 if (toInsert.size() > 0) {
2646 sortChildren(parentNode, all, true, true);
2648 int[] newNodeIndices = new int[toInsert.size()];
2649 int eachNewNodeIndex = 0;
2650 TreeMap<Integer, TreeNode> insertSet = new TreeMap<Integer, TreeNode>();
2651 for (int i = 0; i < toInsert.size(); i++) {
2652 TreeNode eachNewNode = toInsert.get(i);
2653 while (all.get(eachNewNodeIndex) != eachNewNode) {
2654 eachNewNodeIndex++;
2656 newNodeIndices[i] = eachNewNodeIndex;
2657 insertSet.put(eachNewNodeIndex, eachNewNode);
2660 Iterator<Integer> indices = insertSet.keySet().iterator();
2661 while (indices.hasNext()) {
2662 Integer eachIndex = indices.next();
2663 TreeNode eachNode = insertSet.get(eachIndex);
2664 parentNode.insert((MutableTreeNode)eachNode, eachIndex);
2667 myTreeModel.nodesWereInserted(parentNode, newNodeIndices);
2669 else {
2670 ArrayList<TreeNode> before = new ArrayList<TreeNode>();
2671 before.addAll(all);
2673 sortChildren(parentNode, all, true, false);
2674 if (!before.equals(all)) {
2675 processInnerChange(new Runnable() {
2676 public void run() {
2677 parentNode.removeAllChildren();
2678 for (TreeNode each : all) {
2679 parentNode.add((MutableTreeNode)each);
2681 myTreeModel.nodeStructureChanged(parentNode);
2688 private void sortChildren(DefaultMutableTreeNode node, ArrayList<TreeNode> children, boolean updateStamp, boolean forceSort) {
2689 NodeDescriptor descriptor = getDescriptorFrom(node);
2690 assert descriptor != null;
2692 if (descriptor.getChildrenSortingStamp() >= getComparatorStamp() && !forceSort) return;
2693 if (children.size() > 0) {
2694 getBuilder().sortChildren(myNodeComparator, node, children);
2697 if (updateStamp) {
2698 descriptor.setChildrenSortingStamp(getComparatorStamp());
2702 private void disposeNode(DefaultMutableTreeNode node) {
2703 TreeNode parent = node.getParent();
2704 if (parent instanceof DefaultMutableTreeNode) {
2705 addToUnbuilt((DefaultMutableTreeNode)parent);
2708 if (node.getChildCount() > 0) {
2709 for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
2710 disposeNode(_node);
2714 removeFromUpdating(node);
2715 removeFromUnbuilt(node);
2717 if (isLoadingNode(node)) return;
2718 NodeDescriptor descriptor = getDescriptorFrom(node);
2719 if (descriptor == null) return;
2720 final Object element = getElementFromDescriptor(descriptor);
2721 removeMapping(element, node, null);
2722 myAutoExpandRoots.remove(element);
2723 node.setUserObject(null);
2724 node.removeAllChildren();
2727 public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root) {
2728 return addSubtreeToUpdate(root, null);
2731 public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
2732 Object element = getElementFor(root);
2733 if (getTreeStructure().isAlwaysLeaf(element)) {
2734 removeLoading(root, true);
2736 if (runAfterUpdate != null) {
2737 getReady(this).doWhenDone(runAfterUpdate);
2739 return false;
2742 if (isReleaseRequested()) {
2743 processNodeActionsIfReady(root);
2744 } else {
2745 getUpdater().runAfterUpdate(runAfterUpdate);
2746 getUpdater().addSubtreeToUpdate(root);
2749 return true;
2752 public boolean wasRootNodeInitialized() {
2753 return myRootNodeWasInitialized;
2756 private boolean isRootNodeBuilt() {
2757 return myRootNodeWasInitialized && isNodeBeingBuilt(myRootNode);
2760 public void select(final Object[] elements, @Nullable final Runnable onDone) {
2761 select(elements, onDone, false);
2764 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
2765 select(elements, onDone, addToSelection, false);
2768 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) {
2769 _select(elements, onDone, addToSelection, true, false, true, deferred, false, false);
2772 void _select(final Object[] elements,
2773 final Runnable onDone,
2774 final boolean addToSelection,
2775 final boolean checkCurrentSelection,
2776 final boolean checkIfInStructure) {
2778 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, true, false, false, false);
2781 void _select(final Object[] elements,
2782 final Runnable onDone,
2783 final boolean addToSelection,
2784 final boolean checkCurrentSelection,
2785 final boolean checkIfInStructure,
2786 final boolean scrollToVisible) {
2788 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, false, false, false);
2791 public void userSelect(final Object[] elements,
2792 final Runnable onDone,
2793 final boolean addToSelection,
2794 boolean scroll) {
2795 _select(elements, onDone, addToSelection, true, false, scroll, false, true, true);
2798 void _select(final Object[] elements,
2799 final Runnable onDone,
2800 final boolean addToSelection,
2801 final boolean checkCurrentSelection,
2802 final boolean checkIfInStructure,
2803 final boolean scrollToVisible,
2804 final boolean deferred,
2805 final boolean canSmartExpand,
2806 final boolean mayQueue) {
2808 AbstractTreeUpdater updater = getUpdater();
2809 if (mayQueue && updater != null) {
2810 updater.queueSelection(new SelectionRequest(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, deferred, canSmartExpand));
2811 return;
2814 boolean willAffectSelection = elements.length > 0 || (elements.length == 0 && addToSelection);
2815 if (!willAffectSelection) {
2816 runDone(onDone);
2817 return;
2820 final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections;
2822 if (!deferred && wasRootNodeInitialized() && willAffectSelection) {
2823 myCanProcessDeferredSelections = false;
2826 if (!checkDeferred(deferred, onDone)) return;
2828 if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) {
2829 getTree().clearSelection();
2833 runDone(new Runnable() {
2834 public void run() {
2835 if (!checkDeferred(deferred, onDone)) return;
2837 final Set<Object> currentElements = getSelectedElements();
2839 if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
2840 boolean runSelection = false;
2841 for (Object eachToSelect : elements) {
2842 if (!currentElements.contains(eachToSelect)) {
2843 runSelection = true;
2844 break;
2848 if (!runSelection) {
2849 if (elements.length > 0) {
2850 selectVisible(elements[0], onDone, true, true, scrollToVisible);
2852 return;
2856 Set<Object> toSelect = new HashSet<Object>();
2857 myTree.clearSelection();
2858 toSelect.addAll(Arrays.asList(elements));
2859 if (addToSelection) {
2860 toSelect.addAll(currentElements);
2863 if (checkIfInStructure) {
2864 final Iterator<Object> allToSelect = toSelect.iterator();
2865 while (allToSelect.hasNext()) {
2866 Object each = allToSelect.next();
2867 if (!isInStructure(each)) {
2868 allToSelect.remove();
2873 final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
2875 if (wasRootNodeInitialized()) {
2876 final int[] originalRows = myTree.getSelectionRows();
2877 if (!addToSelection) {
2878 myTree.clearSelection();
2880 addNext(elementsToSelect, 0, new Runnable() {
2881 public void run() {
2882 if (getTree().isSelectionEmpty()) {
2883 processInnerChange(new Runnable() {
2884 public void run() {
2885 restoreSelection(currentElements);
2889 runDone(onDone);
2891 }, originalRows, deferred, scrollToVisible, canSmartExpand);
2893 else {
2894 addToDeferred(elementsToSelect, onDone);
2900 private void restoreSelection(Set<Object> selection) {
2901 for (Object each : selection) {
2902 DefaultMutableTreeNode node = getNodeForElement(each, false);
2903 if (node != null && isValidForSelectionAdjusting(node)) {
2904 addSelectionPath(getPathFor(node), false, null, null);
2910 private void addToDeferred(final Object[] elementsToSelect, final Runnable onDone) {
2911 myDeferredSelections.clear();
2912 myDeferredSelections.add(new Runnable() {
2913 public void run() {
2914 select(elementsToSelect, onDone, false, true);
2919 private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) {
2920 if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) {
2921 return true;
2923 else {
2924 runDone(onDone);
2925 return false;
2929 @NotNull
2930 final Set<Object> getSelectedElements() {
2931 final TreePath[] paths = myTree.getSelectionPaths();
2933 Set<Object> result = new HashSet<Object>();
2934 if (paths != null) {
2935 for (TreePath eachPath : paths) {
2936 if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2937 final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
2938 final Object eachElement = getElementFor(eachNode);
2939 if (eachElement != null) {
2940 result.add(eachElement);
2945 return result;
2949 private void addNext(final Object[] elements,
2950 final int i,
2951 @Nullable final Runnable onDone,
2952 final int[] originalRows,
2953 final boolean deferred,
2954 final boolean scrollToVisible,
2955 final boolean canSmartExpand) {
2956 if (i >= elements.length) {
2957 if (myTree.isSelectionEmpty()) {
2958 myTree.setSelectionRows(originalRows);
2960 runDone(onDone);
2962 else {
2963 if (!checkDeferred(deferred, onDone)) {
2964 return;
2967 doSelect(elements[i], new Runnable() {
2968 public void run() {
2969 if (!checkDeferred(deferred, onDone)) return;
2971 addNext(elements, i + 1, onDone, originalRows, deferred, scrollToVisible, canSmartExpand);
2973 }, true, deferred, i == 0, scrollToVisible, canSmartExpand);
2977 public void select(final Object element, @Nullable final Runnable onDone) {
2978 select(element, onDone, false);
2981 public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
2982 _select(new Object[]{element}, onDone, addToSelection, true, false);
2985 private void doSelect(final Object element,
2986 final Runnable onDone,
2987 final boolean addToSelection,
2988 final boolean deferred,
2989 final boolean canBeCentered,
2990 final boolean scrollToVisible,
2991 boolean canSmartExpand) {
2992 final Runnable _onDone = new Runnable() {
2993 public void run() {
2994 if (!checkDeferred(deferred, onDone)) return;
2995 selectVisible(element, onDone, addToSelection, canBeCentered, scrollToVisible);
2998 _expand(element, _onDone, true, false, canSmartExpand);
3001 public void scrollSelectionToVisible(@Nullable Runnable onDone, boolean shouldBeCentered) {
3002 int[] rows = myTree.getSelectionRows();
3003 if (rows == null || rows.length == 0) {
3004 runDone(onDone);
3005 return;
3009 Object toSelect = null;
3010 for (int eachRow : rows) {
3011 TreePath path = myTree.getPathForRow(eachRow);
3012 toSelect = getElementFor(path.getLastPathComponent());
3013 if (toSelect != null) break;
3016 if (toSelect != null) {
3017 selectVisible(toSelect, onDone, true, shouldBeCentered, true);
3021 private void selectVisible(Object element, final Runnable onDone, boolean addToSelection, boolean canBeCentered, final boolean scroll) {
3022 final DefaultMutableTreeNode toSelect = getNodeForElement(element, false);
3024 if (toSelect == null) {
3025 runDone(onDone);
3026 return;
3029 if (getRootNode() == toSelect && !myTree.isRootVisible()) {
3030 runDone(onDone);
3031 return;
3034 final int row = myTree.getRowForPath(new TreePath(toSelect.getPath()));
3036 if (myUpdaterState != null) {
3037 myUpdaterState.addSelection(element);
3040 if (Registry.is("ide.tree.autoscrollToVCenter") && canBeCentered) {
3041 runDone(new Runnable() {
3042 public void run() {
3043 TreeUtil.showRowCentered(myTree, row, false, scroll).doWhenDone(new Runnable() {
3044 public void run() {
3045 runDone(onDone);
3051 else {
3052 TreeUtil.showAndSelect(myTree, row - 2, row + 2, row, -1, addToSelection, scroll).doWhenDone(new Runnable() {
3053 public void run() {
3054 runDone(onDone);
3060 public void expand(final Object element, @Nullable final Runnable onDone) {
3061 expand(new Object[]{element}, onDone);
3064 public void expand(final Object[] element, @Nullable final Runnable onDone) {
3065 expand(element, onDone, false);
3069 void expand(final Object element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
3070 _expand(new Object[]{element}, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure, false);
3073 void expand(final Object[] element, @Nullable final Runnable onDone, boolean checkIfInStructure) {
3074 _expand(element, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure, false);
3077 void _expand(final Object[] element,
3078 @NotNull final Runnable onDone,
3079 final boolean parentsOnly,
3080 final boolean checkIfInStructure,
3081 final boolean canSmartExpand) {
3083 runDone(new Runnable() {
3084 public void run() {
3085 if (element.length == 0) {
3086 runDone(onDone);
3087 return;
3090 if (myUpdaterState != null) {
3091 myUpdaterState.clearExpansion();
3095 final ActionCallback done = new ActionCallback(element.length);
3096 done.doWhenDone(new Runnable() {
3097 public void run() {
3098 runDone(onDone);
3100 }).doWhenRejected(new Runnable() {
3101 public void run() {
3102 runDone(onDone);
3106 expandNext(element, 0, parentsOnly, checkIfInStructure, canSmartExpand, done);
3111 private void expandNext(final Object[] elements, final int index, final boolean parentsOnly, final boolean checkIfInStricture, final boolean canSmartExpand, final ActionCallback done) {
3112 if (elements.length <= 0) {
3113 done.setDone();
3114 return;
3117 if (index >= elements.length) {
3118 return;
3121 _expand(elements[index], new Runnable() {
3122 public void run() {
3123 done.setDone();
3124 expandNext(elements, index + 1, parentsOnly, checkIfInStricture, canSmartExpand, done);
3126 }, parentsOnly, checkIfInStricture, canSmartExpand);
3129 public void collapseChildren(final Object element, @Nullable final Runnable onDone) {
3130 runDone(new Runnable() {
3131 public void run() {
3132 final DefaultMutableTreeNode node = getNodeForElement(element, false);
3133 if (node != null) {
3134 getTree().collapsePath(new TreePath(node.getPath()));
3135 runDone(onDone);
3141 private void runDone(@Nullable Runnable done) {
3142 if (done == null) return;
3144 if (isYeildingNow()) {
3145 if (!myYeildingDoneRunnables.contains(done)) {
3146 myYeildingDoneRunnables.add(done);
3149 else {
3150 done.run();
3154 private void _expand(final Object element,
3155 @NotNull final Runnable onDone,
3156 final boolean parentsOnly,
3157 boolean checkIfInStructure,
3158 boolean canSmartExpand) {
3160 if (checkIfInStructure && !isInStructure(element)) {
3161 runDone(onDone);
3162 return;
3165 if (wasRootNodeInitialized()) {
3166 List<Object> kidsToExpand = new ArrayList<Object>();
3167 Object eachElement = element;
3168 DefaultMutableTreeNode firstVisible = null;
3169 while (true) {
3170 if (!isValid(eachElement)) break;
3172 firstVisible = getNodeForElement(eachElement, true);
3173 if (eachElement != element || !parentsOnly) {
3174 assert !kidsToExpand.contains(eachElement) :
3175 "Not a valid tree structure, walking up the structure gives many entries for element=" +
3176 eachElement +
3177 ", root=" +
3178 getTreeStructure().getRootElement();
3179 kidsToExpand.add(eachElement);
3181 if (firstVisible != null) break;
3182 eachElement = getTreeStructure().getParentElement(eachElement);
3183 if (eachElement == null) {
3184 firstVisible = null;
3185 break;
3190 if (firstVisible == null) {
3191 runDone(onDone);
3193 else if (kidsToExpand.size() == 0) {
3194 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)firstVisible.getParent();
3195 if (parentNode != null) {
3196 final TreePath parentPath = new TreePath(parentNode.getPath());
3197 if (!myTree.isExpanded(parentPath)) {
3198 expand(parentPath, canSmartExpand);
3201 runDone(onDone);
3203 else {
3204 processExpand(firstVisible, kidsToExpand, kidsToExpand.size() - 1, onDone, canSmartExpand);
3207 else {
3208 deferExpansion(element, onDone, parentsOnly, canSmartExpand);
3212 private void deferExpansion(final Object element, final Runnable onDone, final boolean parentsOnly, final boolean canSmartExpand) {
3213 myDeferredExpansions.add(new Runnable() {
3214 public void run() {
3215 _expand(element, onDone, parentsOnly, false, canSmartExpand);
3220 private void processExpand(final DefaultMutableTreeNode toExpand,
3221 final List kidsToExpand,
3222 final int expandIndex,
3223 @NotNull final Runnable onDone,
3224 final boolean canSmartExpand) {
3226 final Object element = getElementFor(toExpand);
3227 if (element == null) {
3228 runDone(onDone);
3229 return;
3232 addNodeAction(element, new NodeAction() {
3233 public void onReady(final DefaultMutableTreeNode node) {
3234 if (node.getChildCount() > 0 && !myTree.isExpanded(new TreePath(node.getPath()))) {
3235 if (!isAutoExpand(node)) {
3236 expand(node, canSmartExpand);
3240 if (expandIndex <= 0) {
3241 runDone(onDone);
3242 return;
3245 final DefaultMutableTreeNode nextNode = getNodeForElement(kidsToExpand.get(expandIndex - 1), false);
3246 if (nextNode != null) {
3247 processExpand(nextNode, kidsToExpand, expandIndex - 1, onDone, canSmartExpand);
3249 else {
3250 runDone(onDone);
3253 }, true);
3256 boolean childrenToUpdate = areChildrenToBeUpdated(toExpand);
3257 boolean expanded = myTree.isExpanded(getPathFor(toExpand));
3258 boolean unbuilt = myUnbuiltNodes.contains(toExpand);
3260 if (expanded) {
3261 if (unbuilt && !childrenToUpdate) {
3262 addSubtreeToUpdate(toExpand);
3264 } else {
3265 expand(toExpand, canSmartExpand);
3268 if (!unbuilt && !childrenToUpdate) {
3269 processNodeActionsIfReady(toExpand);
3273 private boolean areChildrenToBeUpdated(DefaultMutableTreeNode node) {
3274 return getUpdater().isEnqueuedToUpdate(node) || isUpdatingParent(node);
3277 private String asString(DefaultMutableTreeNode node) {
3278 if (node == null) return null;
3280 StringBuffer children = new StringBuffer(node.toString());
3281 children.append(" [");
3282 for (int i = 0; i < node.getChildCount(); i++) {
3283 children.append(node.getChildAt(i));
3284 if (i < node.getChildCount() - 1) {
3285 children.append(",");
3288 children.append("]");
3290 return children.toString();
3293 @Nullable
3294 public Object getElementFor(Object node) {
3295 if (!(node instanceof DefaultMutableTreeNode)) return null;
3296 return getElementFor((DefaultMutableTreeNode)node);
3299 @Nullable
3300 Object getElementFor(DefaultMutableTreeNode node) {
3301 if (node != null) {
3302 final Object o = node.getUserObject();
3303 if (o instanceof NodeDescriptor) {
3304 return getElementFromDescriptor(((NodeDescriptor)o));
3308 return null;
3311 public final boolean isNodeBeingBuilt(final TreePath path) {
3312 return isNodeBeingBuilt(path.getLastPathComponent());
3315 public final boolean isNodeBeingBuilt(Object node) {
3316 if (isReleaseRequested()) return false;
3318 return getParentBuiltNode(node) != null;
3321 public final DefaultMutableTreeNode getParentBuiltNode(Object node) {
3322 DefaultMutableTreeNode parent = getParentLoading(node);
3323 if (parent != null) return parent;
3325 if (isLoadingParent(node)) return (DefaultMutableTreeNode)node;
3327 final boolean childrenAreNoLoadedYet = myUnbuiltNodes.contains(node);
3328 if (childrenAreNoLoadedYet) {
3329 if (node instanceof DefaultMutableTreeNode) {
3330 final TreePath nodePath = new TreePath(((DefaultMutableTreeNode)node).getPath());
3331 if (!myTree.isExpanded(nodePath)) return null;
3334 return (DefaultMutableTreeNode)node;
3338 return null;
3341 private boolean isLoadingParent(Object node) {
3342 if (!(node instanceof DefaultMutableTreeNode)) return false;
3343 return isLoadedInBackground(getElementFor((DefaultMutableTreeNode)node));
3346 public void setTreeStructure(final AbstractTreeStructure treeStructure) {
3347 myTreeStructure = treeStructure;
3348 clearUpdaterState();
3351 public AbstractTreeUpdater getUpdater() {
3352 return myUpdater;
3355 public void setUpdater(final AbstractTreeUpdater updater) {
3356 myUpdater = updater;
3357 if (updater != null && myUpdateIfInactive) {
3358 updater.showNotify();
3361 if (myUpdater != null) {
3362 myUpdater.setPassThroughMode(myPassthroughMode);
3366 public DefaultMutableTreeNode getRootNode() {
3367 return myRootNode;
3370 public void setRootNode(@NotNull final DefaultMutableTreeNode rootNode) {
3371 myRootNode = rootNode;
3374 private void dropUpdaterStateIfExternalChange() {
3375 if (!isInnerChange()) {
3376 clearUpdaterState();
3377 myAutoExpandRoots.clear();
3381 void clearUpdaterState() {
3382 myUpdaterState = null;
3385 private void createMapping(Object element, DefaultMutableTreeNode node) {
3386 if (!myElementToNodeMap.containsKey(element)) {
3387 myElementToNodeMap.put(element, node);
3389 else {
3390 final Object value = myElementToNodeMap.get(element);
3391 final List<DefaultMutableTreeNode> nodes;
3392 if (value instanceof DefaultMutableTreeNode) {
3393 nodes = new ArrayList<DefaultMutableTreeNode>();
3394 nodes.add((DefaultMutableTreeNode)value);
3395 myElementToNodeMap.put(element, nodes);
3397 else {
3398 nodes = (List<DefaultMutableTreeNode>)value;
3400 nodes.add(node);
3404 private void removeMapping(Object element, DefaultMutableTreeNode node, @Nullable Object elementToPutNodeActionsFor) {
3405 final Object value = myElementToNodeMap.get(element);
3406 if (value != null) {
3407 if (value instanceof DefaultMutableTreeNode) {
3408 if (value.equals(node)) {
3409 myElementToNodeMap.remove(element);
3412 else {
3413 List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
3414 final boolean reallyRemoved = nodes.remove(node);
3415 if (reallyRemoved) {
3416 if (nodes.isEmpty()) {
3417 myElementToNodeMap.remove(element);
3423 remapNodeActions(element, elementToPutNodeActionsFor);
3426 private void remapNodeActions(Object element, Object elementToPutNodeActionsFor) {
3427 _remapNodeActions(element, elementToPutNodeActionsFor, myNodeActions);
3428 _remapNodeActions(element, elementToPutNodeActionsFor, myNodeChildrenActions);
3431 private void _remapNodeActions(Object element, Object elementToPutNodeActionsFor, final Map<Object, List<NodeAction>> nodeActions) {
3432 final List<NodeAction> actions = nodeActions.get(element);
3433 nodeActions.remove(element);
3435 if (elementToPutNodeActionsFor != null && actions != null) {
3436 nodeActions.put(elementToPutNodeActionsFor, actions);
3440 private DefaultMutableTreeNode getFirstNode(Object element) {
3441 return findNode(element, 0);
3444 private DefaultMutableTreeNode findNode(final Object element, int startIndex) {
3445 final Object value = getBuilder().findNodeByElement(element);
3446 if (value == null) {
3447 return null;
3449 if (value instanceof DefaultMutableTreeNode) {
3450 return startIndex == 0 ? (DefaultMutableTreeNode)value : null;
3452 final List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value;
3453 return startIndex < nodes.size() ? nodes.get(startIndex) : null;
3456 protected Object findNodeByElement(Object element) {
3457 if (myElementToNodeMap.containsKey(element)) {
3458 return myElementToNodeMap.get(element);
3461 try {
3462 TREE_NODE_WRAPPER.setValue(element);
3463 return myElementToNodeMap.get(TREE_NODE_WRAPPER);
3465 finally {
3466 TREE_NODE_WRAPPER.setValue(null);
3470 private DefaultMutableTreeNode findNodeForChildElement(DefaultMutableTreeNode parentNode, Object element) {
3471 final Object value = myElementToNodeMap.get(element);
3472 if (value == null) {
3473 return null;
3476 if (value instanceof DefaultMutableTreeNode) {
3477 final DefaultMutableTreeNode elementNode = (DefaultMutableTreeNode)value;
3478 return parentNode.equals(elementNode.getParent()) ? elementNode : null;
3481 final List<DefaultMutableTreeNode> allNodesForElement = (List<DefaultMutableTreeNode>)value;
3482 for (final DefaultMutableTreeNode elementNode : allNodesForElement) {
3483 if (parentNode.equals(elementNode.getParent())) {
3484 return elementNode;
3488 return null;
3491 public void cancelBackgroundLoading() {
3492 if (myWorker != null) {
3493 myWorker.cancelTasks();
3494 clearWorkerTasks();
3497 clearNodeActions();
3500 private void addNodeAction(Object element, NodeAction action, boolean shouldChildrenBeReady) {
3501 _addNodeAction(element, action, myNodeActions);
3502 if (shouldChildrenBeReady) {
3503 _addNodeAction(element, action, myNodeChildrenActions);
3508 private void _addNodeAction(Object element, NodeAction action, Map<Object, List<NodeAction>> map) {
3509 maybeSetBusyAndScheduleWaiterForReady(true);
3510 List<NodeAction> list = map.get(element);
3511 if (list == null) {
3512 list = new ArrayList<NodeAction>();
3513 map.put(element, list);
3515 list.add(action);
3519 private void cleanUpNow() {
3520 if (isReleaseRequested()) return;
3522 final UpdaterTreeState state = new UpdaterTreeState(this);
3524 myTree.collapsePath(new TreePath(myTree.getModel().getRoot()));
3525 myTree.clearSelection();
3526 getRootNode().removeAllChildren();
3528 myRootNodeWasInitialized = false;
3529 clearNodeActions();
3530 myElementToNodeMap.clear();
3531 myDeferredSelections.clear();
3532 myDeferredExpansions.clear();
3533 myLoadedInBackground.clear();
3534 myUnbuiltNodes.clear();
3535 myUpdateFromRootRequested = true;
3537 if (myWorker != null) {
3538 Disposer.dispose(myWorker);
3539 myWorker = null;
3542 myTree.invalidate();
3544 state.restore(null);
3547 public AbstractTreeUi setClearOnHideDelay(final long clearOnHideDelay) {
3548 myClearOnHideDelay = clearOnHideDelay;
3549 return this;
3552 public void setJantorPollPeriod(final long time) {
3553 myJanitorPollPeriod = time;
3556 public void setCheckStructure(final boolean checkStructure) {
3557 myCheckStructure = checkStructure;
3560 private class MySelectionListener implements TreeSelectionListener {
3561 public void valueChanged(final TreeSelectionEvent e) {
3562 dropUpdaterStateIfExternalChange();
3567 private class MyExpansionListener implements TreeExpansionListener {
3568 public void treeExpanded(TreeExpansionEvent event) {
3569 dropUpdaterStateIfExternalChange();
3571 TreePath path = event.getPath();
3573 if (myRequestedExpand != null && !myRequestedExpand.equals(path)) return;
3575 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
3577 if (!myUnbuiltNodes.contains(node)) {
3578 removeLoading(node, false);
3580 Set<DefaultMutableTreeNode> childrenToUpdate = new HashSet<DefaultMutableTreeNode>();
3581 for (int i = 0; i < node.getChildCount(); i++) {
3582 DefaultMutableTreeNode each = (DefaultMutableTreeNode)node.getChildAt(i);
3583 if (myUnbuiltNodes.contains(each)) {
3584 makeLoadingOrLeafIfNoChildren(each);
3585 childrenToUpdate.add(each);
3589 if (childrenToUpdate.size() > 0) {
3590 for (DefaultMutableTreeNode each : childrenToUpdate) {
3591 maybeUpdateSubtreeToUpdate(each);
3595 else {
3596 getBuilder().expandNodeChildren(node);
3599 processSmartExpand(node, canSmartExpand(node, true), false);
3600 processNodeActionsIfReady(node);
3603 public void treeCollapsed(TreeExpansionEvent e) {
3604 dropUpdaterStateIfExternalChange();
3606 final TreePath path = e.getPath();
3607 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
3608 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
3611 TreePath pathToSelect = null;
3612 if (isSelectionInside(node)) {
3613 pathToSelect = new TreePath(node.getPath());
3617 NodeDescriptor descriptor = getDescriptorFrom(node);
3618 if (getBuilder().isDisposeOnCollapsing(descriptor)) {
3619 runDone(new Runnable() {
3620 public void run() {
3621 if (isDisposed(node)) return;
3623 TreePath nodePath = new TreePath(node.getPath());
3624 if (myTree.isExpanded(nodePath)) return;
3626 removeChildren(node);
3627 makeLoadingOrLeafIfNoChildren(node);
3630 if (node.equals(getRootNode())) {
3631 if (myTree.isRootVisible()) {
3632 //todo kirillk to investigate -- should be done by standard selction move
3633 //addSelectionPath(new TreePath(getRootNode().getPath()), true, Condition.FALSE);
3636 else {
3637 myTreeModel.reload(node);
3641 if (pathToSelect != null && myTree.isSelectionEmpty()) {
3642 addSelectionPath(pathToSelect, true, Condition.FALSE, null);
3646 private void removeChildren(DefaultMutableTreeNode node) {
3647 EnumerationCopy copy = new EnumerationCopy(node.children());
3648 while (copy.hasMoreElements()) {
3649 disposeNode((DefaultMutableTreeNode)copy.nextElement());
3651 node.removeAllChildren();
3652 myTreeModel.nodeStructureChanged(node);
3656 private void maybeUpdateSubtreeToUpdate(final DefaultMutableTreeNode subtreeRoot) {
3657 if (!myUnbuiltNodes.contains(subtreeRoot)) return;
3658 TreePath path = getPathFor(subtreeRoot);
3660 if (myTree.getRowForPath(path) == -1) return;
3662 DefaultMutableTreeNode parent = getParentBuiltNode(subtreeRoot);
3663 if (parent == null) {
3664 addSubtreeToUpdate(subtreeRoot);
3665 } else if (parent != subtreeRoot) {
3666 addNodeAction(getElementFor(subtreeRoot), new NodeAction() {
3667 public void onReady(DefaultMutableTreeNode parent) {
3668 maybeUpdateSubtreeToUpdate(subtreeRoot);
3670 }, true);
3674 private boolean isSelectionInside(DefaultMutableTreeNode parent) {
3675 TreePath path = new TreePath(myTreeModel.getPathToRoot(parent));
3676 TreePath[] paths = myTree.getSelectionPaths();
3677 if (paths == null) return false;
3678 for (TreePath path1 : paths) {
3679 if (path.isDescendant(path1)) return true;
3681 return false;
3684 public boolean isInStructure(@Nullable Object element) {
3685 Object eachParent = element;
3686 while (eachParent != null) {
3687 if (getTreeStructure().getRootElement().equals(eachParent)) return true;
3688 eachParent = getTreeStructure().getParentElement(eachParent);
3691 return false;
3694 interface NodeAction {
3695 void onReady(DefaultMutableTreeNode node);
3698 public void setCanYield(final boolean canYield) {
3699 myCanYield = canYield;
3702 public Collection<TreeUpdatePass> getYeildingPasses() {
3703 return myYeildingPasses;
3706 public boolean isBuilt(Object element) {
3707 if (!myElementToNodeMap.containsKey(element)) return false;
3708 final Object node = myElementToNodeMap.get(element);
3709 return !myUnbuiltNodes.contains(node);
3712 static class LoadedChildren {
3714 private List myElements;
3715 private Map<Object, NodeDescriptor> myDescriptors = new HashMap<Object, NodeDescriptor>();
3716 private Map<NodeDescriptor, Boolean> myChanges = new HashMap<NodeDescriptor, Boolean>();
3718 LoadedChildren(Object[] elements) {
3719 myElements = Arrays.asList(elements != null ? elements : new Object[0]);
3722 void putDescriptor(Object element, NodeDescriptor descriptor, boolean isChanged) {
3723 assert myElements.contains(element);
3724 myDescriptors.put(element, descriptor);
3725 myChanges.put(descriptor, isChanged);
3728 List getElements() {
3729 return myElements;
3732 NodeDescriptor getDescriptor(Object element) {
3733 return myDescriptors.get(element);
3736 @Override
3737 public String toString() {
3738 return Arrays.asList(myElements) + "->" + myChanges;
3741 public boolean isUpdated(Object element) {
3742 NodeDescriptor desc = getDescriptor(element);
3743 return myChanges.get(desc);
3747 UpdaterTreeState getUpdaterState() {
3748 return myUpdaterState;
3751 private ActionCallback addReadyCallback(Object requestor) {
3752 synchronized (myReadyCallbacks) {
3753 ActionCallback cb = myReadyCallbacks.get(requestor);
3754 if (cb == null) {
3755 cb = new ActionCallback();
3756 myReadyCallbacks.put(requestor, cb);
3759 return cb;
3763 private ActionCallback[] getReadyCallbacks(boolean clear) {
3764 synchronized (myReadyCallbacks) {
3765 ActionCallback[] result = myReadyCallbacks.values().toArray(new ActionCallback[myReadyCallbacks.size()]);
3766 if (clear) {
3767 myReadyCallbacks.clear();
3769 return result;
3773 private long getComparatorStamp() {
3774 if (myNodeDescriptorComparator instanceof NodeDescriptor.NodeComparator) {
3775 long currentComparatorStamp = ((NodeDescriptor.NodeComparator)myNodeDescriptorComparator).getStamp();
3776 if (currentComparatorStamp > myLastComparatorStamp) {
3777 myOwnComparatorStamp = Math.max(myOwnComparatorStamp, currentComparatorStamp) + 1;
3779 myLastComparatorStamp = currentComparatorStamp;
3781 return Math.max(currentComparatorStamp, myOwnComparatorStamp);
3783 else {
3784 return myOwnComparatorStamp;
3788 public void incComparatorStamp() {
3789 myOwnComparatorStamp = getComparatorStamp() + 1;
3792 public static class UpdateInfo {
3793 NodeDescriptor myDescriptor;
3794 TreeUpdatePass myPass;
3795 boolean myCanSmartExpand;
3796 boolean myWasExpanded;
3797 boolean myForceUpdate;
3798 boolean myDescriptorIsUpToDate;
3800 public UpdateInfo(NodeDescriptor descriptor,
3801 TreeUpdatePass pass,
3802 boolean canSmartExpand,
3803 boolean wasExpanded,
3804 boolean forceUpdate,
3805 boolean descriptorIsUpToDate) {
3806 myDescriptor = descriptor;
3807 myPass = pass;
3808 myCanSmartExpand = canSmartExpand;
3809 myWasExpanded = wasExpanded;
3810 myForceUpdate = forceUpdate;
3811 myDescriptorIsUpToDate = descriptorIsUpToDate;
3814 synchronized NodeDescriptor getDescriptor() {
3815 return myDescriptor;
3818 synchronized TreeUpdatePass getPass() {
3819 return myPass;
3822 synchronized boolean isCanSmartExpand() {
3823 return myCanSmartExpand;
3826 synchronized boolean isWasExpanded() {
3827 return myWasExpanded;
3830 synchronized boolean isForceUpdate() {
3831 return myForceUpdate;
3834 synchronized boolean isDescriptorIsUpToDate() {
3835 return myDescriptorIsUpToDate;
3838 public synchronized void apply(UpdateInfo updateInfo) {
3839 myDescriptor = updateInfo.myDescriptor;
3840 myPass = updateInfo.myPass;
3841 myCanSmartExpand = updateInfo.myCanSmartExpand;
3842 myWasExpanded = updateInfo.myWasExpanded;
3843 myForceUpdate = updateInfo.myForceUpdate;
3844 myDescriptorIsUpToDate = updateInfo.myDescriptorIsUpToDate;
3847 public String toString() {
3848 return "UpdateInfo: desc=" + myDescriptor + " pass=" + myPass + " canSmartExpand=" + myCanSmartExpand + " wasExpanded=" + myWasExpanded + " forceUpdate=" + myForceUpdate + " descriptorUpToDate=" + myDescriptorIsUpToDate;
3853 public void setPassthroughMode(boolean passthrough) {
3854 myPassthroughMode = passthrough;
3855 AbstractTreeUpdater updater = getUpdater();
3857 if (updater != null) {
3858 updater.setPassThroughMode(myPassthroughMode);
3861 if (!isUnitTestingMode() && passthrough) {
3862 LOG.error("Pass-through mode for TreeUi is allowed only for unit test mode");
3866 public boolean isPassthroughMode() {
3867 return myPassthroughMode;
3870 private boolean isUnitTestingMode() {
3871 Application app = ApplicationManager.getApplication();
3872 return app != null && app.isUnitTestMode();