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;
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.*;
52 import java.awt.event.FocusAdapter;
53 import java.awt.event.FocusEvent;
54 import java.security.AccessControlException;
56 import java.util.List;
57 import java.util.concurrent.ScheduledExecutorService;
58 import java.util.concurrent.TimeUnit;
60 public class AbstractTreeUi {
61 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder");
62 protected JTree myTree;// protected for TestNG
63 @SuppressWarnings({"WeakerAccess"}) protected DefaultTreeModel myTreeModel;
64 private AbstractTreeStructure myTreeStructure;
65 private AbstractTreeUpdater myUpdater;
67 private Comparator<NodeDescriptor> myNodeDescriptorComparator;
68 private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() {
69 public int compare(TreeNode n1, TreeNode n2) {
70 if (isLoadingNode(n1) || isLoadingNode(n2)) return 0;
71 NodeDescriptor nodeDescriptor1 = getDescriptorFrom(((DefaultMutableTreeNode)n1));
72 NodeDescriptor nodeDescriptor2 = getDescriptorFrom(((DefaultMutableTreeNode)n2));
73 return myNodeDescriptorComparator != null
74 ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2)
75 : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex();
78 long myOwnComparatorStamp;
79 long myLastComparatorStamp;
81 private DefaultMutableTreeNode myRootNode;
82 private final HashMap<Object, Object> myElementToNodeMap = new HashMap<Object, Object>();
83 private final HashSet<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>();
84 private TreeExpansionListener myExpansionListener;
85 private MySelectionListener mySelectionListener;
87 private WorkerThread myWorker = null;
88 private final Set<Runnable> myActiveWorkerTasks = new HashSet<Runnable>();
90 private ProgressIndicator myProgress;
91 private static final int WAIT_CURSOR_DELAY = 100;
92 private AbstractTreeNode<Object> TREE_NODE_WRAPPER;
93 private boolean myRootNodeWasInitialized = false;
94 private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>();
95 private boolean myUpdateFromRootRequested;
96 private boolean myWasEverShown;
97 private boolean myUpdateIfInactive;
99 private final Map<Object, UpdateInfo> myLoadedInBackground = new HashMap<Object, UpdateInfo>();
100 private final Map<Object, List<NodeAction>> myNodeChildrenActions = new HashMap<Object, List<NodeAction>>();
102 private long myClearOnHideDelay = -1;
103 private ScheduledExecutorService ourClearanceService;
104 private final Map<AbstractTreeUi, Long> ourUi2Countdown = Collections.synchronizedMap(new WeakHashMap<AbstractTreeUi, Long>());
106 private final Set<Runnable> myDeferredSelections = new HashSet<Runnable>();
107 private final Set<Runnable> myDeferredExpansions = new HashSet<Runnable>();
109 private boolean myCanProcessDeferredSelections;
111 private UpdaterTreeState myUpdaterState;
112 private AbstractTreeBuilder myBuilder;
114 private final Set<DefaultMutableTreeNode> myUpdatingChildren = new HashSet<DefaultMutableTreeNode>();
115 private long myJanitorPollPeriod = Time.SECOND * 10;
116 private boolean myCheckStructure = false;
119 private boolean myCanYield = false;
121 private final List<TreeUpdatePass> myYeildingPasses = new ArrayList<TreeUpdatePass>();
123 private boolean myYeildingNow;
125 private final Set<DefaultMutableTreeNode> myPendingNodeActions = new HashSet<DefaultMutableTreeNode>();
126 private final Set<Runnable> myYeildingDoneRunnables = new HashSet<Runnable>();
128 private final Alarm myBusyAlarm = new Alarm();
129 private final Runnable myWaiterForReady = new Runnable() {
131 maybeSetBusyAndScheduleWaiterForReady(false);
135 private final RegistryValue myYeildingUpdate = Registry.get("ide.tree.yeildingUiUpdate");
136 private final RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator");
137 private final RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimout");
139 private boolean myWasEverIndexNotReady;
140 private boolean myShowing;
141 private FocusAdapter myFocusListener = new FocusAdapter() {
143 public void focusGained(FocusEvent e) {
147 private Set<DefaultMutableTreeNode> myNotForSmartExpand = new HashSet<DefaultMutableTreeNode>();
148 private TreePath myRequestedExpand;
149 private ActionCallback myInitialized = new ActionCallback();
150 private Map<Object, ActionCallback> myReadyCallbacks = new WeakHashMap<Object, ActionCallback>();
152 private boolean myPassthroughMode = false;
155 protected void init(AbstractTreeBuilder builder,
157 DefaultTreeModel treeModel,
158 AbstractTreeStructure treeStructure,
159 @Nullable Comparator<NodeDescriptor> comparator,
160 boolean updateIfInactive) {
163 myTreeModel = treeModel;
164 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
165 myTree.setModel(myTreeModel);
166 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
167 setTreeStructure(treeStructure);
168 myNodeDescriptorComparator = comparator;
169 myUpdateIfInactive = updateIfInactive;
171 myExpansionListener = new MyExpansionListener();
172 myTree.addTreeExpansionListener(myExpansionListener);
174 mySelectionListener = new MySelectionListener();
175 myTree.addTreeSelectionListener(mySelectionListener);
177 setUpdater(getBuilder().createUpdater());
178 myProgress = getBuilder().createProgressIndicator();
179 Disposer.register(getBuilder(), getUpdater());
181 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
182 public void showNotify() {
184 myWasEverShown = true;
190 public void hideNotify() {
197 Disposer.register(getBuilder(), uiNotify);
199 myTree.addFocusListener(myFocusListener);
203 boolean isNodeActionsPending() {
204 return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty();
207 private void clearNodeActions() {
208 myNodeActions.clear();
209 myNodeChildrenActions.clear();
212 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
213 if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
215 if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
216 final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
217 final boolean isBusy = !isReady() || forcedBusy;
218 if (isBusy && tree.isShowing()) {
219 tree.setPaintBusy(true);
220 myBusyAlarm.cancelAllRequests();
221 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
224 tree.setPaintBusy(false);
229 private void initClearanceServiceIfNeeded() {
230 if (ourClearanceService != null) return;
232 ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor", Thread.MIN_PRIORITY + 1);
233 ourClearanceService.scheduleWithFixedDelay(new Runnable() {
237 }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
240 private void cleanUpAll() {
241 final long now = System.currentTimeMillis();
242 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
243 for (AbstractTreeUi eachUi : uis) {
244 if (eachUi == null) continue;
245 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
246 if (timeToCleanup == null) continue;
247 if (now >= timeToCleanup.longValue()) {
248 ourUi2Countdown.remove(eachUi);
249 Runnable runnable = new Runnable() {
251 getBuilder().cleanUp();
254 if (isPassthroughMode()) {
257 UIUtil.invokeAndWaitIfNeeded(runnable);
263 protected void doCleanUp() {
264 Runnable cleanup = new Runnable() {
272 if (isPassthroughMode()) {
275 UIUtil.invokeLaterIfNeeded(cleanup);
279 private void disposeClearanceService() {
281 if (ourClearanceService != null) {
282 ourClearanceService.shutdown();
283 ourClearanceService = null;
286 catch (AccessControlException e) {
291 public void activate(boolean byShowing) {
292 myCanProcessDeferredSelections = true;
293 ourUi2Countdown.remove(this);
295 if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) {
296 getBuilder().updateFromRoot();
299 getUpdater().showNotify();
301 myWasEverShown |= byShowing;
304 public void deactivate() {
305 getUpdater().hideNotify();
306 myBusyAlarm.cancelAllRequests();
308 if (!myWasEverShown) return;
310 if (isNodeActionsPending()) {
311 cancelBackgroundLoading();
312 myUpdateFromRootRequested = true;
315 if (getClearOnHideDelay() >= 0) {
316 ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
317 initClearanceServiceIfNeeded();
322 public void release() {
323 if (isReleased()) return;
325 myTree.removeTreeExpansionListener(myExpansionListener);
326 myTree.removeTreeSelectionListener(mySelectionListener);
327 myTree.removeFocusListener(myFocusListener);
329 disposeNode(getRootNode());
330 myElementToNodeMap.clear();
331 getUpdater().cancelAllRequests();
332 if (myWorker != null) {
333 myWorker.dispose(true);
336 TREE_NODE_WRAPPER.setValue(null);
337 if (myProgress != null) {
340 disposeClearanceService();
345 //todo [kirillk] afraid to do so just in release day, to uncomment
346 // myTreeStructure = null;
351 myDeferredSelections.clear();
352 myDeferredExpansions.clear();
353 myYeildingDoneRunnables.clear();
356 public boolean isReleased() {
357 return myBuilder == null;
360 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
361 if (!myUnbuiltNodes.contains(node)) return;
362 if (isLoadedInBackground(getElementFor(node))) return;
364 getTreeStructure().commit();
365 addSubtreeToUpdate(node);
366 getUpdater().performUpdate();
369 public final AbstractTreeStructure getTreeStructure() {
370 return myTreeStructure;
373 public final JTree getTree() {
378 private NodeDescriptor getDescriptorFrom(DefaultMutableTreeNode node) {
379 return (NodeDescriptor)node.getUserObject();
383 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
384 DefaultMutableTreeNode result = null;
385 if (validateAgainstStructure) {
388 final DefaultMutableTreeNode node = findNode(element, index);
389 if (node == null) break;
391 if (isNodeValidForElement(element, node)) {
400 result = getFirstNode(element);
404 if (result != null && !isNodeInStructure(result)) {
412 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
413 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
416 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
417 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
420 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
421 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
422 final Object parentElement = getElementFor(parent);
423 if (!isInStructure(parentElement)) return false;
425 if (parent instanceof ElementNode) {
426 return ((ElementNode)parent).isValidChild(element);
429 for (int i = 0; i < parent.getChildCount(); i++) {
430 final TreeNode child = parent.getChildAt(i);
431 final Object eachElement = getElementFor(child);
432 if (element.equals(eachElement)) return true;
439 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
440 boolean valid = true;
442 if (eachParent == null) {
443 valid = eachParentNode == null;
447 if (!eachParent.equals(getElementFor(eachParentNode))) {
452 eachParent = getTreeStructure().getParentElement(eachParent);
453 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
458 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
459 DefaultMutableTreeNode node = null;
460 for (final Object pathElement : path) {
461 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
469 public final void buildNodeForElement(Object element) {
470 getUpdater().performUpdate();
471 DefaultMutableTreeNode node = getNodeForElement(element, false);
473 final java.util.List<Object> elements = new ArrayList<Object>();
475 element = getTreeStructure().getParentElement(element);
476 if (element == null) {
479 elements.add(0, element);
482 for (final Object element1 : elements) {
483 node = getNodeForElement(element1, false);
491 public final void buildNodeForPath(Object[] path) {
492 getUpdater().performUpdate();
493 DefaultMutableTreeNode node = null;
494 for (final Object pathElement : path) {
495 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
496 if (node != null && node != path[path.length - 1]) {
502 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
503 myNodeDescriptorComparator = nodeDescriptorComparator;
504 myLastComparatorStamp = -1;
505 getBuilder().queueUpdateFrom(getTreeStructure().getRootElement(), true);
508 protected AbstractTreeBuilder getBuilder() {
512 protected final void initRootNode() {
513 if (myUpdateIfInactive) {
517 myUpdateFromRootRequested = true;
521 private void initRootNodeNowIfNeeded(final TreeUpdatePass pass) {
522 if (myRootNodeWasInitialized) return;
524 myRootNodeWasInitialized = true;
526 final Object rootElement = getTreeStructure().getRootElement();
527 addNodeAction(rootElement, new NodeAction() {
528 public void onReady(final DefaultMutableTreeNode node) {
529 processDeferredActions();
534 final Ref<NodeDescriptor> rootDescriptor = new Ref<NodeDescriptor>(null);
535 final boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(rootElement);
537 Runnable build = new Runnable() {
539 rootDescriptor.set(getTreeStructure().createDescriptor(rootElement, null));
540 getRootNode().setUserObject(rootDescriptor.get());
541 update(rootDescriptor.get(), true);
546 Runnable update = new Runnable() {
548 if (getElementFromDescriptor(rootDescriptor.get()) != null) {
549 createMapping(getElementFromDescriptor(rootDescriptor.get()), getRootNode());
553 insertLoadingNode(getRootNode(), true);
555 boolean willUpdate = false;
556 if (isAutoExpand(rootDescriptor.get())) {
557 willUpdate = myUnbuiltNodes.contains(getRootNode());
558 expand(getRootNode(), true);
561 updateNodeChildren(getRootNode(), pass, null, false, false, false, true);
563 if (getRootNode().getChildCount() == 0) {
564 myTreeModel.nodeChanged(getRootNode());
570 queueToBackground(build, update, null);
578 private boolean isAutoExpand(NodeDescriptor descriptor) {
579 boolean autoExpand = false;
581 if (descriptor != null) {
582 autoExpand = getBuilder().isAutoExpandNode(descriptor);
585 if (!autoExpand && !myTree.isRootVisible()) {
586 Object element = getElementFromDescriptor(descriptor);
587 if (element != null && element.equals(getTreeStructure().getRootElement())) return true;
593 private boolean isAutoExpand(DefaultMutableTreeNode node) {
594 return isAutoExpand(getDescriptorFrom(node));
597 private AsyncResult<Boolean> update(final NodeDescriptor nodeDescriptor, boolean now) {
598 final AsyncResult<Boolean> result = new AsyncResult<Boolean>();
600 if (now || isPassthroughMode()) {
601 return new AsyncResult<Boolean>().setDone(_update(nodeDescriptor));
604 Object element = getElementFromDescriptor(nodeDescriptor);
605 boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(element);
607 boolean edt = isEdt();
610 final Ref<Boolean> changes = new Ref<Boolean>(false);
611 queueToBackground(new Runnable() {
613 changes.set(_update(nodeDescriptor));
617 result.setDone(changes.get());
622 result.setDone(_update(nodeDescriptor));
626 if (edt || !myWasEverShown) {
627 result.setDone(_update(nodeDescriptor));
630 UIUtil.invokeLaterIfNeeded(new Runnable() {
633 result.setDone(_update(nodeDescriptor));
636 result.setRejected();
643 result.doWhenDone(new AsyncResult.Handler<Boolean>() {
644 public void run(Boolean changes) {
646 final long updateStamp = nodeDescriptor.getUpdateCount();
647 UIUtil.invokeLaterIfNeeded(new Runnable() {
649 Object element = nodeDescriptor.getElement();
650 DefaultMutableTreeNode node = getNodeForElement(element, false);
652 TreePath path = getPathFor(node);
653 if (path != null && myTree.isVisible(path)) {
654 updateNodeImageAndPosition(node, false);
667 private boolean _update(NodeDescriptor nodeDescriptor) {
668 nodeDescriptor.setUpdateCount(nodeDescriptor.getUpdateCount() + 1);
669 return getBuilder().updateNodeDescriptor(nodeDescriptor);
672 private void assertIsDispatchThread() {
673 if (isPassthroughMode()) return;
675 if (isTreeShowing() && !isEdt()) {
676 LOG.error("Must be in event-dispatch thread");
680 private boolean isEdt() {
681 return SwingUtilities.isEventDispatchThread();
684 private boolean isTreeShowing() {
688 private void assertNotDispatchThread() {
689 if (isPassthroughMode()) return;
692 LOG.error("Must not be in event-dispatch thread");
696 private void processDeferredActions() {
697 processDeferredActions(myDeferredSelections);
698 processDeferredActions(myDeferredExpansions);
701 private void processDeferredActions(Set<Runnable> actions) {
702 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
704 for (Runnable runnable : runnables) {
709 //todo: to make real callback
710 public ActionCallback queueUpdate(Object element) {
711 AbstractTreeUpdater updater = getUpdater();
712 if (updater == null) {
713 return new ActionCallback.Rejected();
716 final ActionCallback result = new ActionCallback();
717 DefaultMutableTreeNode node = getNodeForElement(element, false);
719 addSubtreeToUpdate(node);
722 addSubtreeToUpdate(getRootNode());
725 updater.runAfterUpdate(new Runnable() {
733 public void doUpdateFromRoot() {
734 updateSubtree(getRootNode(), false);
737 public ActionCallback doUpdateFromRootCB() {
738 final ActionCallback cb = new ActionCallback();
739 getUpdater().runAfterUpdate(new Runnable() {
744 updateSubtree(getRootNode(), false);
748 public final void updateSubtree(DefaultMutableTreeNode node, boolean canSmartExpand) {
749 updateSubtree(new TreeUpdatePass(node), canSmartExpand);
752 public final void updateSubtree(TreeUpdatePass pass, boolean canSmartExpand) {
753 if (getUpdater() != null) {
754 getUpdater().addSubtreeToUpdate(pass);
757 updateSubtreeNow(pass, canSmartExpand);
761 final void updateSubtreeNow(TreeUpdatePass pass, boolean canSmartExpand) {
762 maybeSetBusyAndScheduleWaiterForReady(true);
764 initRootNodeNowIfNeeded(pass);
766 final DefaultMutableTreeNode node = pass.getNode();
768 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
770 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
772 boolean forceUpdate = true;
773 TreePath path = getPathFor(node);
774 boolean invisible = !myTree.isExpanded(path) && (path.getParentPath() == null || !myTree.isExpanded(path.getParentPath()));
776 if (invisible && myUnbuiltNodes.contains(node)) {
780 updateNodeChildren(node, pass, null, false, canSmartExpand, forceUpdate, false);
783 private boolean isToBuildInBackground(NodeDescriptor descriptor) {
784 return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor));
788 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
789 final UpdaterTreeState oldState = myUpdaterState;
790 if (oldState == null) {
791 myUpdaterState = state;
795 oldState.addAll(state);
800 protected void doUpdateNode(final DefaultMutableTreeNode node) {
801 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
802 final NodeDescriptor descriptor = getDescriptorFrom(node);
803 final Object prevElement = getElementFromDescriptor(descriptor);
804 if (prevElement == null) return;
805 update(descriptor, false).doWhenDone(new AsyncResult.Handler<Boolean>() {
806 public void run(Boolean changes) {
807 if (!isValid(descriptor)) {
808 if (isInStructure(prevElement)) {
809 getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement));
814 updateNodeImageAndPosition(node, true);
820 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
821 return getBuilder().getTreeStructureElement(descriptor);
824 private void updateNodeChildren(final DefaultMutableTreeNode node,
825 final TreeUpdatePass pass,
826 @Nullable LoadedChildren loadedChildren,
828 final boolean toSmartExpand,
830 final boolean descriptorIsUpToDate) {
832 getTreeStructure().commit();
835 final NodeDescriptor descriptor = getDescriptorFrom(node);
836 if (descriptor == null) {
837 removeLoading(node, true);
841 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node);
842 final boolean wasLeaf = node.getChildCount() == 0;
845 boolean bgBuild = isToBuildInBackground(descriptor);
846 boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded;
848 if (notRequiredToUpdateChildren && forceUpdate && !wasExpanded) {
849 boolean alwaysPlus = getBuilder().isAlwaysShowPlus(descriptor);
850 if (alwaysPlus && wasLeaf) {
851 notRequiredToUpdateChildren = false;
853 notRequiredToUpdateChildren = alwaysPlus;
857 final Ref<LoadedChildren> preloaded = new Ref<LoadedChildren>(loadedChildren);
858 boolean descriptorWasUpdated = descriptorIsUpToDate;
860 if (notRequiredToUpdateChildren) {
861 if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) {
862 insertLoadingNode(node, true);
869 if (myUnbuiltNodes.contains(node)) {
870 if (!descriptorWasUpdated) {
871 update(descriptor, true);
872 descriptorWasUpdated = true;
875 if (processAlwaysLeaf(node)) return;
877 Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null);
878 if (unbuilt.getFirst()) return;
879 preloaded.set(unbuilt.getSecond());
885 final boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded);
887 if (!forcedNow && isToBuildInBackground(descriptor)) {
888 if (processAlwaysLeaf(node)) return;
890 queueBackgroundUpdate(
891 new UpdateInfo(descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorWasUpdated), node);
895 if (!descriptorWasUpdated) {
896 update(descriptor, false).doWhenDone(new Runnable() {
898 if (processAlwaysLeaf(node)) return;
899 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
904 if (processAlwaysLeaf(node)) return;
906 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
911 processNodeActionsIfReady(node);
915 private boolean processAlwaysLeaf(DefaultMutableTreeNode node) {
916 Object element = getElementFor(node);
917 NodeDescriptor desc = getDescriptorFrom(node);
919 if (desc == null) return false;
921 if (getTreeStructure().isAlwaysLeaf(element)) {
922 removeLoading(node, true);
924 if (node.getChildCount() > 0) {
925 final TreeNode[] children = new TreeNode[node.getChildCount()];
926 for (int i = 0; i < node.getChildCount(); i++) {
927 children[i] = node.getChildAt(i);
930 if (isSelectionInside(node)) {
931 addSelectionPath(getPathFor(node), true, Condition.TRUE, null);
934 doWithUpdaterState(new Runnable() {
936 for (TreeNode each : children) {
937 removeNodeFromParent((MutableTreeNode)each, true);
938 disposeNode((DefaultMutableTreeNode)each);
944 removeFromUnbuilt(node);
945 desc.setWasDeclaredAlwaysLeaf(true);
946 processNodeActionsIfReady(node);
949 boolean wasLeaf = desc.isWasDeclaredAlwaysLeaf();
950 desc.setWasDeclaredAlwaysLeaf(false);
953 insertLoadingNode(node, true);
960 private boolean isChildNodeForceUpdate(DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) {
961 TreePath path = getPathFor(node);
962 return parentForceUpdate && (parentExpanded || myTree.isExpanded(path));
965 private void updateNodeChildrenNow(final DefaultMutableTreeNode node,
966 final TreeUpdatePass pass,
967 final LoadedChildren preloadedChildren,
968 final boolean toSmartExpand,
969 final boolean wasExpanded,
970 final boolean wasLeaf,
971 final boolean forceUpdate) {
972 final NodeDescriptor descriptor = getDescriptorFrom(node);
974 final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren);
975 final LoadedChildren loadedChildren =
976 preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray());
980 pass.setCurrentNode(node);
982 final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
984 processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren)
985 .doWhenDone(new Runnable() {
987 if (isDisposed(node)) {
991 removeLoading(node, false);
993 final boolean expanded = isExpanded(node, wasExpanded);
995 collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren)
996 .doWhenDone(new AsyncResult.Handler<ArrayList<TreeNode>>() {
997 public void run(ArrayList<TreeNode> nodesToInsert) {
998 insertNodesInto(nodesToInsert, node);
999 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
1000 removeLoading(node, true);
1001 removeFromUpdating(node);
1003 if (node.getChildCount() > 0) {
1005 expand(node, canSmartExpand);
1009 final Object element = getElementFor(node);
1010 addNodeAction(element, new NodeAction() {
1011 public void onReady(final DefaultMutableTreeNode node) {
1012 removeLoading(node, false);
1016 processNodeActionsIfReady(node);
1023 private boolean isDisposed(DefaultMutableTreeNode node) {
1024 return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
1027 private void expand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1028 expand(new TreePath(node.getPath()), canSmartExpand);
1031 private void expand(final TreePath path, boolean canSmartExpand) {
1032 if (path == null) return;
1035 final Object last = path.getLastPathComponent();
1036 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
1037 final boolean isRoot = last == myTree.getModel().getRoot();
1038 final TreePath parent = path.getParentPath();
1039 if (isRoot && !myTree.isExpanded(path)) {
1040 if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) {
1041 insertLoadingNode((DefaultMutableTreeNode)last, false);
1043 expandPath(path, canSmartExpand);
1045 else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last))) {
1046 if (last instanceof DefaultMutableTreeNode) {
1047 processNodeActionsIfReady((DefaultMutableTreeNode)last);
1051 if (isLeaf && myUnbuiltNodes.contains(last)) {
1052 insertLoadingNode((DefaultMutableTreeNode)last, true);
1053 expandPath(path, canSmartExpand);
1055 else if (isLeaf && parent != null) {
1056 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
1057 if (parentNode != null) {
1058 addToUnbuilt(parentNode);
1060 expandPath(parent, canSmartExpand);
1063 expandPath(path, canSmartExpand);
1068 private void addToUnbuilt(DefaultMutableTreeNode node) {
1069 myUnbuiltNodes.add(node);
1072 private void removeFromUnbuilt(DefaultMutableTreeNode node) {
1073 myUnbuiltNodes.remove(node);
1076 private Pair<Boolean, LoadedChildren> processUnbuilt(final DefaultMutableTreeNode node,
1077 final NodeDescriptor descriptor,
1078 final TreeUpdatePass pass,
1080 final LoadedChildren loadedChildren) {
1081 if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) {
1082 return new Pair<Boolean, LoadedChildren>(true, null);
1085 final Object element = getElementFor(node);
1087 final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element));
1091 if (children.getElements().size() == 0) {
1092 removeLoading(node, true);
1096 if (isAutoExpand(node)) {
1097 addNodeAction(getElementFor(node), new NodeAction() {
1098 public void onReady(final DefaultMutableTreeNode node) {
1099 final TreePath path = new TreePath(node.getPath());
1100 if (getTree().isExpanded(path) || children.getElements().size() == 0) {
1101 removeLoading(node, false);
1104 maybeYeild(new ActiveRunnable() {
1105 public ActionCallback run() {
1106 expand(element, null);
1107 return new ActionCallback.Done();
1117 processNodeActionsIfReady(node);
1119 return new Pair<Boolean, LoadedChildren>(processed, children);
1122 private boolean removeIfLoading(TreeNode node) {
1123 if (isLoadingNode(node)) {
1124 moveSelectionToParentIfNeeded(node);
1125 removeNodeFromParent((MutableTreeNode)node, false);
1132 private void moveSelectionToParentIfNeeded(TreeNode node) {
1133 TreePath path = getPathFor(node);
1134 if (myTree.getSelectionModel().isPathSelected(path)) {
1135 TreePath parentPath = path.getParentPath();
1136 myTree.getSelectionModel().removeSelectionPath(path);
1137 if (parentPath != null) {
1138 myTree.getSelectionModel().addSelectionPath(parentPath);
1143 //todo [kirillk] temporary consistency check
1144 private Object[] getChildrenFor(final Object element) {
1145 final Object[] passOne;
1147 passOne = getTreeStructure().getChildElements(element);
1149 catch (IndexNotReadyException e) {
1150 if (!myWasEverIndexNotReady) {
1151 myWasEverIndexNotReady = true;
1152 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
1154 return ArrayUtil.EMPTY_OBJECT_ARRAY;
1157 if (!myCheckStructure) return passOne;
1159 final Object[] passTwo = getTreeStructure().getChildElements(element);
1161 final HashSet two = new HashSet(Arrays.asList(passTwo));
1163 if (passOne.length != passTwo.length) {
1165 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1169 for (Object eachInOne : passOne) {
1170 if (!two.contains(eachInOne)) {
1172 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1182 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert,
1183 TreeUpdatePass pass,
1184 boolean canSmartExpand,
1185 boolean forceUpdate) {
1186 for (TreeNode aNodesToInsert : nodesToInsert) {
1187 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
1188 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
1192 private ActionCallback processExistingNodes(final DefaultMutableTreeNode node,
1193 final MutualMap<Object, Integer> elementToIndexMap,
1194 final TreeUpdatePass pass,
1195 final boolean canSmartExpand,
1196 final boolean forceUpdate,
1197 final boolean wasExpaned,
1198 final LoadedChildren preloaded) {
1200 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
1201 return maybeYeild(new ActiveRunnable() {
1202 public ActionCallback run() {
1203 if (pass.isExpired()) return new ActionCallback.Rejected();
1204 if (childNodes.size() == 0) return new ActionCallback.Done();
1207 final ActionCallback result = new ActionCallback(childNodes.size());
1209 for (TreeNode each : childNodes) {
1210 final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each;
1211 if (isLoadingNode(eachChild)) {
1216 final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned);
1218 maybeYeild(new ActiveRunnable() {
1220 public ActionCallback run() {
1221 return processExistingNode(eachChild, getDescriptorFrom(eachChild), node, elementToIndexMap, pass, canSmartExpand,
1222 childForceUpdate, preloaded);
1224 }, pass, node).notify(result);
1226 if (result.isRejected()) {
1236 private boolean isRerunNeeded(TreeUpdatePass pass) {
1237 if (pass.isExpired()) return false;
1239 final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
1241 return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
1244 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
1245 final ActionCallback result = new ActionCallback();
1247 if (isRerunNeeded(pass)) {
1248 getUpdater().addSubtreeToUpdate(pass);
1249 result.setRejected();
1252 if (isToYieldUpdateFor(node)) {
1253 pass.setCurrentNode(node);
1254 yieldAndRun(new Runnable() {
1256 if (pass.isExpired()) return;
1258 if (isRerunNeeded(pass)) {
1259 runDone(new Runnable() {
1261 if (!pass.isExpired()) {
1262 getUpdater().addSubtreeToUpdate(pass);
1266 result.setRejected();
1269 processRunnable.run().notify(result);
1275 processRunnable.run().notify(result);
1282 private void yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
1283 myYeildingPasses.add(pass);
1284 myYeildingNow = true;
1285 yield(new Runnable() {
1291 runOnYieldingDone(new Runnable() {
1296 executeYieldingRequest(runnable, pass);
1303 public boolean isYeildingNow() {
1304 return myYeildingNow;
1307 private boolean hasSheduledUpdates() {
1308 return getUpdater().hasNodesToUpdate() || isLoadingInBackgroundNow();
1311 public boolean isReady() {
1312 return isIdle() && !hasPendingWork() && !isNodeActionsPending();
1315 public boolean hasPendingWork() {
1316 return hasNodesToUpdate() || (myUpdaterState != null && myUpdaterState.isProcessingNow());
1319 public boolean isIdle() {
1320 return !isYeildingNow() && !isWorkerBusy() && (!hasSheduledUpdates() || getUpdater().isInPostponeMode());
1323 private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
1325 myYeildingPasses.remove(pass);
1329 maybeYeildingFinished();
1333 private void maybeYeildingFinished() {
1334 if (myYeildingPasses.size() == 0) {
1335 myYeildingNow = false;
1336 flushPendingNodeActions();
1341 if (isReleased()) return;
1344 if (myTree.isShowing() || myUpdateIfInactive) {
1345 myInitialized.setDone();
1348 if (myTree.isShowing()) {
1349 if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
1350 TreeUtil.ensureSelection(myTree);
1354 if (myInitialized.isDone()) {
1355 for (ActionCallback each : getReadyCallbacks(true)) {
1362 private void flushPendingNodeActions() {
1363 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
1364 myPendingNodeActions.clear();
1366 for (DefaultMutableTreeNode each : nodes) {
1367 processNodeActionsIfReady(each);
1370 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
1371 for (Runnable each : actions) {
1372 if (!isYeildingNow()) {
1373 myYeildingDoneRunnables.remove(each);
1381 protected void runOnYieldingDone(Runnable onDone) {
1382 getBuilder().runOnYeildingDone(onDone);
1385 protected void yield(Runnable runnable) {
1386 getBuilder().yield(runnable);
1389 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
1390 if (!canYield()) return false;
1391 return getBuilder().isToYieldUpdateFor(node);
1394 private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor,
1395 @Nullable LoadedChildren preloadedChildren) {
1396 MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
1397 List children = preloadedChildren != null
1398 ? preloadedChildren.getElements()
1399 : Arrays.asList(getChildrenFor(getBuilder().getTreeStructureElement(descriptor)));
1401 for (Object child : children) {
1402 if (!isValid(child)) continue;
1403 elementToIndexMap.put(child, Integer.valueOf(index));
1406 return elementToIndexMap;
1409 private void expand(final DefaultMutableTreeNode node,
1410 final NodeDescriptor descriptor,
1411 final boolean wasLeaf,
1412 final boolean canSmartExpand) {
1413 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1414 alarm.addRequest(new Runnable() {
1416 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1418 }, WAIT_CURSOR_DELAY);
1420 if (wasLeaf && isAutoExpand(descriptor)) {
1421 expand(node, canSmartExpand);
1424 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1425 for (TreeNode node1 : nodes) {
1426 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1427 if (isLoadingNode(childNode)) continue;
1428 NodeDescriptor childDescr = getDescriptorFrom(childNode);
1429 if (isAutoExpand(childDescr)) {
1430 addNodeAction(getElementFor(childNode), new NodeAction() {
1431 public void onReady(DefaultMutableTreeNode node) {
1432 expand(childNode, canSmartExpand);
1435 addSubtreeToUpdate(childNode);
1439 int n = alarm.cancelAllRequests();
1441 myTree.setCursor(Cursor.getDefaultCursor());
1445 public static boolean isLoadingNode(final Object node) {
1446 return node instanceof LoadingNode;
1449 private AsyncResult<ArrayList<TreeNode>> collectNodesToInsert(final NodeDescriptor descriptor,
1450 final MutualMap<Object, Integer> elementToIndexMap,
1451 final DefaultMutableTreeNode parent,
1452 final boolean addLoadingNode,
1453 @NotNull final LoadedChildren loadedChildren) {
1454 final AsyncResult<ArrayList<TreeNode>> result = new AsyncResult<ArrayList<TreeNode>>();
1456 final ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1457 final Collection<Object> allElements = elementToIndexMap.getKeys();
1459 final ActionCallback processingDone = new ActionCallback(allElements.size());
1461 for (final Object child : allElements) {
1462 Integer index = elementToIndexMap.getValue(child);
1463 final Ref<NodeDescriptor> childDescr = new Ref<NodeDescriptor>(loadedChildren.getDescriptor(child));
1464 boolean needToUpdate = false;
1465 if (childDescr.get() == null) {
1466 childDescr.set(getTreeStructure().createDescriptor(child, descriptor));
1467 needToUpdate = true;
1470 //noinspection ConstantConditions
1471 if (childDescr.get() == null) {
1472 LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1473 processingDone.setDone();
1476 childDescr.get().setIndex(index.intValue());
1478 final ActionCallback update = new ActionCallback();
1480 update(childDescr.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1481 public void run(Boolean changes) {
1482 loadedChildren.putDescriptor(child, childDescr.get(), changes);
1491 update.doWhenDone(new Runnable() {
1493 Object element = getElementFromDescriptor(childDescr.get());
1494 if (element == null) {
1495 processingDone.setDone();
1498 DefaultMutableTreeNode node = getNodeForElement(element, false);
1499 if (node == null || node.getParent() != parent) {
1500 final DefaultMutableTreeNode childNode = createChildNode(childDescr.get());
1501 if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr.get())) {
1502 insertLoadingNode(childNode, true);
1505 addToUnbuilt(childNode);
1507 nodesToInsert.add(childNode);
1508 createMapping(element, childNode);
1510 processingDone.setDone();
1516 processingDone.doWhenDone(new Runnable() {
1518 result.setDone(nodesToInsert);
1525 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1526 return new ElementNode(this, descriptor);
1529 protected boolean canYield() {
1530 return myCanYield && myYeildingUpdate.asBoolean();
1533 public long getClearOnHideDelay() {
1534 return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
1537 public ActionCallback getInitialized() {
1538 return myInitialized;
1541 public ActionCallback getReady(Object requestor) {
1543 return new ActionCallback.Done();
1546 return addReadyCallback(requestor);
1550 private void addToUpdating(DefaultMutableTreeNode node) {
1551 synchronized (myUpdatingChildren) {
1552 myUpdatingChildren.add(node);
1556 private void removeFromUpdating(DefaultMutableTreeNode node) {
1557 synchronized (myUpdatingChildren) {
1558 myUpdatingChildren.remove(node);
1562 public boolean isUpdatingNow(DefaultMutableTreeNode node) {
1563 synchronized (myUpdatingChildren) {
1564 return myUpdatingChildren.contains(node);
1568 boolean hasUpdatingNow() {
1569 synchronized (myUpdatingChildren) {
1570 return myUpdatingChildren.size() > 0;
1574 public Map getNodeActions() {
1575 return myNodeActions;
1578 public List<Object> getLoadedChildrenFor(Object element) {
1579 List<Object> result = new ArrayList<Object>();
1581 DefaultMutableTreeNode node = (DefaultMutableTreeNode)getNodeForElement(element, false);
1583 for (int i = 0; i < node.getChildCount(); i++) {
1584 TreeNode each = node.getChildAt(i);
1585 if (isLoadingNode(each)) continue;
1587 result.add(getElementFor(each));
1594 public boolean hasNodesToUpdate() {
1595 return getUpdater().hasNodesToUpdate() || hasUpdatingNow() || isLoadingInBackgroundNow();
1598 public List<Object> getExpandedElements() {
1599 List<Object> result = new ArrayList<Object>();
1600 Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode()));
1601 while (enumeration.hasMoreElements()) {
1602 TreePath each = enumeration.nextElement();
1603 Object eachElement = getElementFor(each.getLastPathComponent());
1604 if (eachElement != null) {
1605 result.add(eachElement);
1612 static class ElementNode extends DefaultMutableTreeNode {
1614 Set<Object> myElements = new HashSet<Object>();
1615 AbstractTreeUi myUi;
1617 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1623 public void insert(final MutableTreeNode newChild, final int childIndex) {
1624 super.insert(newChild, childIndex);
1625 final Object element = myUi.getElementFor(newChild);
1626 if (element != null) {
1627 myElements.add(element);
1632 public void remove(final int childIndex) {
1633 final TreeNode node = getChildAt(childIndex);
1634 super.remove(childIndex);
1635 final Object element = myUi.getElementFor(node);
1636 if (element != null) {
1637 myElements.remove(element);
1641 boolean isValidChild(Object childElement) {
1642 return myElements.contains(childElement);
1646 public String toString() {
1647 return String.valueOf(getUserObject());
1651 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1652 return getUpdatingParent(kid) != null;
1655 private DefaultMutableTreeNode getUpdatingParent(DefaultMutableTreeNode kid) {
1656 DefaultMutableTreeNode eachParent = kid;
1657 while (eachParent != null) {
1658 if (isUpdatingNow(eachParent)) return eachParent;
1659 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1665 private boolean isLoadedInBackground(Object element) {
1666 return getLoadedInBackground(element) != null;
1669 private UpdateInfo getLoadedInBackground(Object element) {
1670 synchronized (myLoadedInBackground) {
1671 return myLoadedInBackground.get(element);
1675 private void addToLoadedInBackground(Object element, UpdateInfo info) {
1676 synchronized (myLoadedInBackground) {
1677 myLoadedInBackground.put(element, info);
1681 private void removeFromLoadedInBackground(final Object element) {
1682 synchronized (myLoadedInBackground) {
1683 myLoadedInBackground.remove(element);
1687 private boolean isLoadingInBackgroundNow() {
1688 synchronized (myLoadedInBackground) {
1689 return myLoadedInBackground.size() > 0;
1693 private boolean queueBackgroundUpdate(final UpdateInfo updateInfo, final DefaultMutableTreeNode node) {
1694 assertIsDispatchThread();
1696 final Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor());
1698 UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor);
1699 if (loaded != null) {
1700 loaded.apply(updateInfo);
1704 addToLoadedInBackground(oldElementFromDescriptor, updateInfo);
1706 if (!isNodeBeingBuilt(node)) {
1707 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1708 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1711 final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
1712 final Ref<Object> elementFromDescriptor = new Ref<Object>();
1713 Runnable buildRunnable = new Runnable() {
1719 if (!updateInfo.isDescriptorIsUpToDate()) {
1720 update(updateInfo.getDescriptor(), true);
1723 Object element = getElementFromDescriptor(updateInfo.getDescriptor());
1724 if (element == null) {
1725 removeFromLoadedInBackground(oldElementFromDescriptor);
1729 elementFromDescriptor.set(element);
1731 Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(updateInfo.getDescriptor()));
1732 LoadedChildren loaded = new LoadedChildren(loadedElements);
1733 for (Object each : loadedElements) {
1734 NodeDescriptor eachChildDescriptor = getTreeStructure().createDescriptor(each, updateInfo.getDescriptor());
1735 loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).getResult());
1738 children.set(loaded);
1742 final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
1743 Runnable updateRunnable = new Runnable() {
1745 if (isReleased()) return;
1746 if (children.get() == null) return;
1748 if (isRerunNeeded(updateInfo.getPass())) {
1749 removeFromLoadedInBackground(elementFromDescriptor.get());
1750 getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1754 removeFromLoadedInBackground(elementFromDescriptor.get());
1756 if (myUnbuiltNodes.contains(node)) {
1757 Pair<Boolean, LoadedChildren> unbuilt =
1758 processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()),
1760 if (unbuilt.getFirst()) {
1761 nodeToProcessActions[0] = node;
1766 updateNodeChildren(node, updateInfo.getPass(), children.get(), true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(),
1770 if (isRerunNeeded(updateInfo.getPass())) {
1771 getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1775 Object element = elementFromDescriptor.get();
1777 if (element != null) {
1778 removeLoading(node, true);
1779 nodeToProcessActions[0] = node;
1783 queueToBackground(buildRunnable, updateRunnable, new Runnable() {
1785 if (nodeToProcessActions[0] != null) {
1786 processNodeActionsIfReady(nodeToProcessActions[0]);
1793 private boolean isExpanded(DefaultMutableTreeNode node, boolean isExpanded) {
1794 return isExpanded || myTree.isExpanded(getPathFor(node));
1797 private void removeLoading(DefaultMutableTreeNode parent, boolean removeFromUnbuilt) {
1798 for (int i = 0; i < parent.getChildCount(); i++) {
1799 TreeNode child = parent.getChildAt(i);
1800 if (removeIfLoading(child)) {
1805 if (removeFromUnbuilt) {
1806 removeFromUnbuilt(parent);
1809 if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
1810 insertLoadingNode(parent, false);
1816 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1817 assertIsDispatchThread();
1819 if (isNodeBeingBuilt(node)) return;
1821 final Object o = node.getUserObject();
1822 if (!(o instanceof NodeDescriptor)) return;
1825 if (isYeildingNow()) {
1826 myPendingNodeActions.add(node);
1830 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1832 boolean childrenReady = !isLoadedInBackground(element);
1834 processActions(node, element, myNodeActions, childrenReady ? myNodeChildrenActions : null);
1835 if (childrenReady) {
1836 processActions(node, element, myNodeChildrenActions, null);
1839 if (!isUpdatingParent(node) && !isWorkerBusy()) {
1840 final UpdaterTreeState state = myUpdaterState;
1841 if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1842 if (!state.restore(childrenReady ? node : null)) {
1843 setUpdaterState(state);
1852 private void processActions(DefaultMutableTreeNode node, Object element, final Map<Object, List<NodeAction>> nodeActions, @Nullable final Map<Object, List<NodeAction>> secondaryNodeAction) {
1853 final List<NodeAction> actions = nodeActions.get(element);
1854 if (actions != null) {
1855 nodeActions.remove(element);
1857 List<NodeAction> secondary = secondaryNodeAction != null ? secondaryNodeAction.get(element) : null;
1858 for (NodeAction each : actions) {
1859 if (secondary != null && secondary.contains(each)) {
1860 secondary.remove(each);
1868 private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1869 return !myNotForSmartExpand.contains(node) && canSmartExpand;
1872 private void processSmartExpand(final DefaultMutableTreeNode node, final boolean canSmartExpand) {
1873 if (!getBuilder().isSmartExpand() || !canSmartExpand(node, canSmartExpand)) return;
1875 if (isNodeBeingBuilt(node)) {
1876 addNodeAction(getElementFor(node), new NodeAction() {
1877 public void onReady(DefaultMutableTreeNode node) {
1878 processSmartExpand(node, canSmartExpand);
1883 TreeNode child = getChildForSmartExpand(node);
1884 if (child != null) {
1885 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child);
1886 myTree.expandPath(childPath);
1892 private TreeNode getChildForSmartExpand(DefaultMutableTreeNode node) {
1893 int realChildCount = 0;
1894 TreeNode nodeToExpand = null;
1896 for (int i = 0; i < node.getChildCount(); i++) {
1897 TreeNode eachChild = node.getChildAt(i);
1899 if (!isLoadingNode(eachChild)) {
1901 if (nodeToExpand == null) {
1902 nodeToExpand = eachChild;
1906 if (realChildCount > 1) {
1907 nodeToExpand = null;
1912 return nodeToExpand;
1915 public boolean isLoadingChildrenFor(final Object nodeObject) {
1916 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1918 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1920 int loadingNodes = 0;
1921 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
1922 TreeNode child = node.getChildAt(i);
1923 if (isLoadingNode(child)) {
1927 return loadingNodes > 0 && loadingNodes == node.getChildCount();
1930 private boolean isParentLoading(Object nodeObject) {
1931 return getParentLoading(nodeObject) != null;
1934 private DefaultMutableTreeNode getParentLoading(Object nodeObject) {
1935 if (!(nodeObject instanceof DefaultMutableTreeNode)) return null;
1937 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1939 TreeNode eachParent = node.getParent();
1941 while (eachParent != null) {
1942 eachParent = eachParent.getParent();
1943 if (eachParent instanceof DefaultMutableTreeNode) {
1944 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
1945 if (isLoadedInBackground(eachElement)) return (DefaultMutableTreeNode)eachParent;
1952 protected String getLoadingNodeText() {
1953 return IdeBundle.message("progress.searching");
1956 private ActionCallback processExistingNode(final DefaultMutableTreeNode childNode,
1957 final NodeDescriptor childDescriptor,
1958 final DefaultMutableTreeNode parentNode,
1959 final MutualMap<Object, Integer> elementToIndexMap,
1960 final TreeUpdatePass pass,
1961 final boolean canSmartExpand,
1962 final boolean forceUpdate,
1963 LoadedChildren parentPreloadedChildren) {
1965 final ActionCallback result = new ActionCallback();
1967 if (pass.isExpired()) {
1968 return new ActionCallback.Rejected();
1971 final Ref<NodeDescriptor> childDesc = new Ref<NodeDescriptor>(childDescriptor);
1973 if (childDesc.get() == null) {
1975 return new ActionCallback.Rejected();
1977 final Object oldElement = getElementFromDescriptor(childDesc.get());
1978 if (oldElement == null) {
1980 return new ActionCallback.Rejected();
1983 AsyncResult<Boolean> update = new AsyncResult<Boolean>();
1984 if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) {
1985 update.setDone(parentPreloadedChildren.isUpdated(oldElement));
1988 update = update(childDesc.get(), false);
1991 update.doWhenDone(new AsyncResult.Handler<Boolean>() {
1992 public void run(Boolean isChanged) {
1993 final Ref<Boolean> changes = new Ref<Boolean>(isChanged);
1995 final Ref<Boolean> forceRemapping = new Ref<Boolean>(false);
1996 final Ref<Object> newElement = new Ref<Object>(getElementFromDescriptor(childDesc.get()));
1998 final Integer index = newElement.get() != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc.get())) : null;
1999 final AsyncResult<Boolean> updateIndexDone = new AsyncResult<Boolean>();
2000 final ActionCallback indexReady = new ActionCallback();
2001 if (index != null) {
2002 final Object elementFromMap = elementToIndexMap.getKey(index);
2003 if (elementFromMap != newElement.get() && elementFromMap.equals(newElement.get())) {
2004 if (isInStructure(elementFromMap) && isInStructure(newElement.get())) {
2005 if (parentNode.getUserObject() instanceof NodeDescriptor) {
2006 final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
2007 childDesc.set(getTreeStructure().createDescriptor(elementFromMap, parentDescriptor));
2008 childNode.setUserObject(childDesc.get());
2009 newElement.set(elementFromMap);
2010 forceRemapping.set(true);
2011 update(childDesc.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
2012 public void run(Boolean isChanged) {
2013 changes.set(isChanged);
2014 updateIndexDone.setDone(isChanged);
2020 updateIndexDone.setDone(changes.get());
2023 updateIndexDone.setDone(changes.get());
2026 updateIndexDone.doWhenDone(new Runnable() {
2028 if (childDesc.get().getIndex() != index.intValue()) {
2031 childDesc.get().setIndex(index.intValue());
2032 indexReady.setDone();
2037 updateIndexDone.setDone();
2040 updateIndexDone.doWhenDone(new Runnable() {
2042 if (index != null && changes.get()) {
2043 updateNodeImageAndPosition(childNode, false);
2045 if (!oldElement.equals(newElement.get()) | forceRemapping.get()) {
2046 removeMapping(oldElement, childNode, newElement.get());
2047 if (newElement.get() != null) {
2048 createMapping(newElement.get(), childNode);
2050 getDescriptorFrom(parentNode).setChildrenSortingStamp(-1);
2053 if (index == null) {
2054 int selectedIndex = -1;
2055 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
2056 selectedIndex = parentNode.getIndex(childNode);
2059 if (childNode.getParent() instanceof DefaultMutableTreeNode) {
2060 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
2061 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
2062 if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
2063 insertLoadingNode(parent, false);
2068 Object disposedElement = getElementFor(childNode);
2070 removeNodeFromParent(childNode, selectedIndex >= 0);
2071 disposeNode(childNode);
2073 adjustSelectionOnChildRemove(parentNode, selectedIndex, disposedElement);
2076 elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc.get()));
2077 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
2080 if (parentNode.equals(getRootNode())) {
2081 myTreeModel.nodeChanged(getRootNode());
2094 private void adjustSelectionOnChildRemove(DefaultMutableTreeNode parentNode, int selectedIndex, Object disposedElement) {
2095 DefaultMutableTreeNode node = getNodeForElement(disposedElement, false);
2096 if (node != null && isValidForSelectionAdjusting(node)) {
2097 Object newElement = getElementFor(node);
2098 addSelectionPath(getPathFor(node), true, getExpiredElementCondition(newElement), disposedElement);
2103 if (selectedIndex >= 0) {
2104 if (parentNode.getChildCount() > 0) {
2105 if (parentNode.getChildCount() > selectedIndex) {
2106 TreeNode newChildNode = parentNode.getChildAt(selectedIndex);
2107 if (isValidForSelectionAdjusting(newChildNode)) {
2108 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement), disposedElement);
2112 TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1);
2113 if (isValidForSelectionAdjusting(newChild)) {
2114 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement), disposedElement);
2119 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement), disposedElement);
2124 private boolean isValidForSelectionAdjusting(TreeNode node) {
2125 if (!myTree.isRootVisible() && getRootNode() == node) return false;
2127 if (isLoadingNode(node)) return true;
2129 final Object elementInTree = getElementFor(node);
2130 if (elementInTree == null) return false;
2132 final TreeNode parentNode = node.getParent();
2133 final Object parentElementInTree = getElementFor(parentNode);
2134 if (parentElementInTree == null) return false;
2136 final Object parentElement = getTreeStructure().getParentElement(elementInTree);
2138 return parentElementInTree.equals(parentElement);
2141 public Condition getExpiredElementCondition(final Object element) {
2142 return new Condition() {
2143 public boolean value(final Object o) {
2144 return isInStructure(element);
2149 private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement, @Nullable final Object adjustmentCause) {
2150 doWithUpdaterState(new Runnable() {
2152 TreePath toSelect = null;
2154 if (isLoadingNode(path.getLastPathComponent())) {
2155 final TreePath parentPath = path.getParentPath();
2156 if (parentPath != null) {
2157 if (isValidForSelectionAdjusting((TreeNode)parentPath.getLastPathComponent())) {
2158 toSelect = parentPath;
2169 if (toSelect != null) {
2170 myTree.addSelectionPath(toSelect);
2172 if (isAdjustedSelection && myUpdaterState != null) {
2173 final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
2174 myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement, adjustmentCause);
2181 private static TreePath getPathFor(TreeNode node) {
2182 if (node instanceof DefaultMutableTreeNode) {
2183 return new TreePath(((DefaultMutableTreeNode)node).getPath());
2186 ArrayList nodes = new ArrayList();
2187 TreeNode eachParent = node;
2188 while (eachParent != null) {
2189 nodes.add(eachParent);
2190 eachParent = eachParent.getParent();
2193 return new TreePath(ArrayUtil.toObjectArray(nodes));
2198 private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
2199 doWithUpdaterState(new Runnable() {
2201 if (willAdjustSelection) {
2202 final TreePath path = getPathFor(node);
2203 if (myTree.isPathSelected(path)) {
2204 myTree.removeSelectionPath(path);
2208 myTreeModel.removeNodeFromParent(node);
2213 private void expandPath(final TreePath path, final boolean canSmartExpand) {
2214 doWithUpdaterState(new Runnable() {
2216 if (path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2217 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2218 if (node.getChildCount() > 0 && !myTree.isExpanded(path)) {
2219 if (!canSmartExpand) {
2220 myNotForSmartExpand.add(node);
2223 myRequestedExpand = path;
2224 myTree.expandPath(path);
2225 processSmartExpand(node, canSmartExpand);
2228 myNotForSmartExpand.remove(node);
2229 myRequestedExpand = null;
2233 processNodeActionsIfReady(node);
2240 private void doWithUpdaterState(Runnable runnable) {
2241 if (myUpdaterState != null) {
2242 myUpdaterState.process(runnable);
2249 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
2250 return descriptor.update();
2253 private void makeLoadingOrLeafIfNoChildren(final DefaultMutableTreeNode node) {
2254 TreePath path = getPathFor(node);
2255 if (path == null) return;
2257 insertLoadingNode(node, true);
2259 final NodeDescriptor descriptor = getDescriptorFrom(node);
2260 if (descriptor == null) return;
2262 descriptor.setChildrenSortingStamp(-1);
2264 if (getBuilder().isAlwaysShowPlus(descriptor)) return;
2267 TreePath parentPath = path.getParentPath();
2268 if (myTree.isVisible(path) || (parentPath != null && myTree.isExpanded(parentPath))) {
2269 if (myTree.isExpanded(path)) {
2270 addSubtreeToUpdate(node);
2273 insertLoadingNode(node, false);
2279 private boolean isValid(DefaultMutableTreeNode node) {
2280 if (node == null) return false;
2281 final Object object = node.getUserObject();
2282 if (object instanceof NodeDescriptor) {
2283 return isValid((NodeDescriptor)object);
2289 private boolean isValid(NodeDescriptor descriptor) {
2290 if (descriptor == null) return false;
2291 return isValid(getElementFromDescriptor(descriptor));
2294 private boolean isValid(Object element) {
2295 if (element instanceof ValidateableNode) {
2296 if (!((ValidateableNode)element).isValid()) return false;
2298 return getBuilder().validateNode(element);
2301 private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
2302 if (!isLoadingChildrenFor(node)) {
2303 myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
2312 protected void queueToBackground(@NotNull final Runnable bgBuildAction,
2313 @Nullable final Runnable edtPostRunnable,
2314 @Nullable final Runnable finalizeEdtRunnable) {
2315 registerWorkerTask(bgBuildAction);
2317 final Runnable pooledThreadWithProgressRunnable = new Runnable() {
2323 final AbstractTreeBuilder builder = getBuilder();
2325 builder.runBackgroundLoading(new Runnable() {
2327 assertNotDispatchThread();
2334 bgBuildAction.run();
2336 if (edtPostRunnable != null && !isReleased()) {
2337 builder.updateAfterLoadedInBackground(new Runnable() {
2340 assertIsDispatchThread();
2346 edtPostRunnable.run();
2349 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2355 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2358 catch (ProcessCanceledException e) {
2359 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2361 catch (Throwable t) {
2362 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2363 throw new RuntimeException(t);
2370 Runnable pooledThreadRunnable = new Runnable() {
2372 if (isReleased()) return;
2375 if (myProgress != null) {
2376 ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress);
2379 pooledThreadWithProgressRunnable.run();
2382 catch (ProcessCanceledException e) {
2388 if (isPassthroughMode()) {
2391 if (myWorker == null || myWorker.isDisposed()) {
2392 myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
2394 myWorker.addTaskFirst(pooledThreadRunnable);
2395 myWorker.dispose(false);
2398 myWorker.addTaskFirst(pooledThreadRunnable);
2403 private void registerWorkerTask(Runnable runnable) {
2404 synchronized (myActiveWorkerTasks) {
2405 myActiveWorkerTasks.add(runnable);
2409 private void unregisterWorkerTask(Runnable runnable, @Nullable Runnable finalizeRunnable) {
2411 synchronized (myActiveWorkerTasks) {
2412 wasRemoved = myActiveWorkerTasks.remove(runnable);
2415 if (wasRemoved && finalizeRunnable != null) {
2416 finalizeRunnable.run();
2422 public boolean isWorkerBusy() {
2423 synchronized (myActiveWorkerTasks) {
2424 return myActiveWorkerTasks.size() > 0;
2428 private void clearWorkerTasks() {
2429 synchronized (myActiveWorkerTasks) {
2430 myActiveWorkerTasks.clear();
2434 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node, boolean updatePosition) {
2435 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2436 NodeDescriptor descriptor = getDescriptorFrom(node);
2437 if (getElementFromDescriptor(descriptor) == null) return;
2439 boolean notified = false;
2440 if (updatePosition) {
2441 DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
2442 if (parentNode != null) {
2443 int oldIndex = parentNode.getIndex(node);
2444 int newIndex = oldIndex;
2445 if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
2446 final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
2447 for (int i = 0; i < parentNode.getChildCount(); i++) {
2448 children.add(parentNode.getChildAt(i));
2450 sortChildren(node, children, true, false);
2451 newIndex = children.indexOf(node);
2454 if (oldIndex != newIndex) {
2455 List<Object> pathsToExpand = new ArrayList<Object>();
2456 List<Object> selectionPaths = new ArrayList<Object>();
2457 TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
2458 removeNodeFromParent(node, false);
2459 myTreeModel.insertNodeInto(node, parentNode, newIndex);
2460 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
2464 myTreeModel.nodeChanged(node);
2469 myTreeModel.nodeChanged(node);
2475 myTreeModel.nodeChanged(node);
2480 public DefaultTreeModel getTreeModel() {
2484 private void insertNodesInto(final ArrayList<TreeNode> toInsert, final DefaultMutableTreeNode parentNode) {
2485 sortChildren(parentNode, toInsert, false, true);
2486 final ArrayList<TreeNode> all = new ArrayList<TreeNode>(toInsert.size() + parentNode.getChildCount());
2487 all.addAll(toInsert);
2488 all.addAll(TreeUtil.childrenToArray(parentNode));
2490 if (toInsert.size() > 0) {
2491 sortChildren(parentNode, all, true, true);
2493 int[] newNodeIndices = new int[toInsert.size()];
2494 int eachNewNodeIndex = 0;
2495 TreeMap<Integer, TreeNode> insertSet = new TreeMap<Integer, TreeNode>();
2496 for (int i = 0; i < toInsert.size(); i++) {
2497 TreeNode eachNewNode = toInsert.get(i);
2498 while (all.get(eachNewNodeIndex) != eachNewNode) {
2501 newNodeIndices[i] = eachNewNodeIndex;
2502 insertSet.put(eachNewNodeIndex, eachNewNode);
2505 Iterator<Integer> indices = insertSet.keySet().iterator();
2506 while (indices.hasNext()) {
2507 Integer eachIndex = indices.next();
2508 TreeNode eachNode = insertSet.get(eachIndex);
2509 parentNode.insert((MutableTreeNode)eachNode, eachIndex);
2512 myTreeModel.nodesWereInserted(parentNode, newNodeIndices);
2515 ArrayList<TreeNode> before = new ArrayList<TreeNode>();
2518 sortChildren(parentNode, all, true, false);
2519 if (!before.equals(all)) {
2520 doWithUpdaterState(new Runnable() {
2522 parentNode.removeAllChildren();
2523 for (TreeNode each : all) {
2524 parentNode.add((MutableTreeNode)each);
2526 myTreeModel.nodeStructureChanged(parentNode);
2533 private void sortChildren(DefaultMutableTreeNode node, ArrayList<TreeNode> children, boolean updateStamp, boolean forceSort) {
2534 NodeDescriptor descriptor = getDescriptorFrom(node);
2535 assert descriptor != null;
2537 if (descriptor.getChildrenSortingStamp() >= getComparatorStamp() && !forceSort) return;
2538 if (children.size() > 0) {
2539 getBuilder().sortChildren(myNodeComparator, node, children);
2543 descriptor.setChildrenSortingStamp(getComparatorStamp());
2547 private void disposeNode(DefaultMutableTreeNode node) {
2548 TreeNode parent = node.getParent();
2549 if (parent instanceof DefaultMutableTreeNode) {
2550 addToUnbuilt((DefaultMutableTreeNode)parent);
2553 if (node.getChildCount() > 0) {
2554 for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
2559 removeFromUpdating(node);
2560 removeFromUnbuilt(node);
2562 if (isLoadingNode(node)) return;
2563 NodeDescriptor descriptor = getDescriptorFrom(node);
2564 if (descriptor == null) return;
2565 final Object element = getElementFromDescriptor(descriptor);
2566 removeMapping(element, node, null);
2567 node.setUserObject(null);
2568 node.removeAllChildren();
2571 public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root) {
2572 return addSubtreeToUpdate(root, null);
2575 public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
2576 Object element = getElementFor(root);
2577 if (getTreeStructure().isAlwaysLeaf(element)) {
2578 removeLoading(root, true);
2580 if (runAfterUpdate != null) {
2581 getReady(this).doWhenDone(runAfterUpdate);
2586 getUpdater().runAfterUpdate(runAfterUpdate);
2587 getUpdater().addSubtreeToUpdate(root);
2592 public boolean wasRootNodeInitialized() {
2593 return myRootNodeWasInitialized;
2596 private boolean isRootNodeBuilt() {
2597 return myRootNodeWasInitialized && isNodeBeingBuilt(myRootNode);
2600 public void select(final Object[] elements, @Nullable final Runnable onDone) {
2601 select(elements, onDone, false);
2604 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
2605 select(elements, onDone, addToSelection, false);
2608 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) {
2609 _select(elements, onDone, addToSelection, true, false, true, deferred, false, false);
2612 void _select(final Object[] elements,
2613 final Runnable onDone,
2614 final boolean addToSelection,
2615 final boolean checkCurrentSelection,
2616 final boolean checkIfInStructure) {
2618 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, true, false, false, false);
2621 void _select(final Object[] elements,
2622 final Runnable onDone,
2623 final boolean addToSelection,
2624 final boolean checkCurrentSelection,
2625 final boolean checkIfInStructure,
2626 final boolean scrollToVisible) {
2628 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, false, false, false);
2631 public void userSelect(final Object[] elements,
2632 final Runnable onDone,
2633 final boolean addToSelection,
2635 _select(elements, onDone, addToSelection, true, false, scroll, false, true, true);
2638 void _select(final Object[] elements,
2639 final Runnable onDone,
2640 final boolean addToSelection,
2641 final boolean checkCurrentSelection,
2642 final boolean checkIfInStructure,
2643 final boolean scrollToVisible,
2644 final boolean deferred,
2645 final boolean canSmartExpand,
2646 final boolean mayQueue) {
2648 AbstractTreeUpdater updater = getUpdater();
2649 if (mayQueue && updater != null) {
2650 updater.queueSelection(new SelectionRequest(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, deferred, canSmartExpand));
2654 boolean willAffectSelection = elements.length > 0 || (elements.length == 0 && addToSelection);
2655 if (!willAffectSelection) {
2660 final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections;
2662 if (!deferred && wasRootNodeInitialized() && willAffectSelection) {
2663 myCanProcessDeferredSelections = false;
2666 if (!checkDeferred(deferred, onDone)) return;
2668 if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) {
2669 getTree().clearSelection();
2673 runDone(new Runnable() {
2675 if (!checkDeferred(deferred, onDone)) return;
2677 final Set<Object> currentElements = getSelectedElements();
2679 if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
2680 boolean runSelection = false;
2681 for (Object eachToSelect : elements) {
2682 if (!currentElements.contains(eachToSelect)) {
2683 runSelection = true;
2688 if (!runSelection) {
2689 if (elements.length > 0) {
2690 selectVisible(elements[0], onDone, true, true, scrollToVisible);
2696 Set<Object> toSelect = new HashSet<Object>();
2697 myTree.clearSelection();
2698 toSelect.addAll(Arrays.asList(elements));
2699 if (addToSelection) {
2700 toSelect.addAll(currentElements);
2703 if (checkIfInStructure) {
2704 final Iterator<Object> allToSelect = toSelect.iterator();
2705 while (allToSelect.hasNext()) {
2706 Object each = allToSelect.next();
2707 if (!isInStructure(each)) {
2708 allToSelect.remove();
2713 final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
2715 if (wasRootNodeInitialized()) {
2716 final int[] originalRows = myTree.getSelectionRows();
2717 if (!addToSelection) {
2718 myTree.clearSelection();
2720 addNext(elementsToSelect, 0, new Runnable() {
2722 if (getTree().isSelectionEmpty()) {
2723 restoreSelection(currentElements);
2727 }, originalRows, deferred, scrollToVisible, canSmartExpand);
2730 addToDeferred(elementsToSelect, onDone);
2736 private void restoreSelection(Set<Object> selection) {
2737 for (Object each : selection) {
2738 DefaultMutableTreeNode node = getNodeForElement(each, false);
2739 if (node != null && isValidForSelectionAdjusting(node)) {
2740 addSelectionPath(getPathFor(node), false, null, null);
2746 private void addToDeferred(final Object[] elementsToSelect, final Runnable onDone) {
2747 myDeferredSelections.clear();
2748 myDeferredSelections.add(new Runnable() {
2750 select(elementsToSelect, onDone, false, true);
2755 private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) {
2756 if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) {
2766 final Set<Object> getSelectedElements() {
2767 final TreePath[] paths = myTree.getSelectionPaths();
2769 Set<Object> result = new HashSet<Object>();
2770 if (paths != null) {
2771 for (TreePath eachPath : paths) {
2772 if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2773 final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
2774 final Object eachElement = getElementFor(eachNode);
2775 if (eachElement != null) {
2776 result.add(eachElement);
2785 private void addNext(final Object[] elements,
2787 @Nullable final Runnable onDone,
2788 final int[] originalRows,
2789 final boolean deferred,
2790 final boolean scrollToVisible,
2791 final boolean canSmartExpand) {
2792 if (i >= elements.length) {
2793 if (myTree.isSelectionEmpty()) {
2794 myTree.setSelectionRows(originalRows);
2799 if (!checkDeferred(deferred, onDone)) {
2803 doSelect(elements[i], new Runnable() {
2805 if (!checkDeferred(deferred, onDone)) return;
2807 addNext(elements, i + 1, onDone, originalRows, deferred, scrollToVisible, canSmartExpand);
2809 }, true, deferred, i == 0, scrollToVisible, canSmartExpand);
2813 public void select(final Object element, @Nullable final Runnable onDone) {
2814 select(element, onDone, false);
2817 public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
2818 _select(new Object[]{element}, onDone, addToSelection, true, false);
2821 private void doSelect(final Object element,
2822 final Runnable onDone,
2823 final boolean addToSelection,
2824 final boolean deferred,
2825 final boolean canBeCentered,
2826 final boolean scrollToVisible,
2827 boolean canSmartExpand) {
2828 final Runnable _onDone = new Runnable() {
2830 if (!checkDeferred(deferred, onDone)) return;
2831 selectVisible(element, onDone, addToSelection, canBeCentered, scrollToVisible);
2834 _expand(element, _onDone, true, false, canSmartExpand);
2837 public void scrollSelectionToVisible(@Nullable Runnable onDone, boolean shouldBeCentered) {
2838 int[] rows = myTree.getSelectionRows();
2839 if (rows == null || rows.length == 0) {
2845 Object toSelect = null;
2846 for (int eachRow : rows) {
2847 TreePath path = myTree.getPathForRow(eachRow);
2848 toSelect = getElementFor(path.getLastPathComponent());
2849 if (toSelect != null) break;
2852 if (toSelect != null) {
2853 selectVisible(toSelect, onDone, true, shouldBeCentered, true);
2857 private void selectVisible(Object element, final Runnable onDone, boolean addToSelection, boolean canBeCentered, final boolean scroll) {
2858 final DefaultMutableTreeNode toSelect = getNodeForElement(element, false);
2860 if (toSelect == null) {