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 private Set<Object> myAutoExpandRoots = new HashSet<Object>();
156 private final RegistryValue myAutoExpandDepth = Registry.get("ide.tree.autoExpandMaxDepth");
158 protected void init(AbstractTreeBuilder builder,
160 DefaultTreeModel treeModel,
161 AbstractTreeStructure treeStructure,
162 @Nullable Comparator<NodeDescriptor> comparator,
163 boolean updateIfInactive) {
166 myTreeModel = treeModel;
167 TREE_NODE_WRAPPER = getBuilder().createSearchingTreeNodeWrapper();
168 myTree.setModel(myTreeModel);
169 setRootNode((DefaultMutableTreeNode)treeModel.getRoot());
170 setTreeStructure(treeStructure);
171 myNodeDescriptorComparator = comparator;
172 myUpdateIfInactive = updateIfInactive;
174 myExpansionListener = new MyExpansionListener();
175 myTree.addTreeExpansionListener(myExpansionListener);
177 mySelectionListener = new MySelectionListener();
178 myTree.addTreeSelectionListener(mySelectionListener);
180 setUpdater(getBuilder().createUpdater());
181 myProgress = getBuilder().createProgressIndicator();
182 Disposer.register(getBuilder(), getUpdater());
184 final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() {
185 public void showNotify() {
187 myWasEverShown = true;
193 public void hideNotify() {
200 Disposer.register(getBuilder(), uiNotify);
202 myTree.addFocusListener(myFocusListener);
206 boolean isNodeActionsPending() {
207 return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty();
210 private void clearNodeActions() {
211 myNodeActions.clear();
212 myNodeChildrenActions.clear();
215 private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy) {
216 if (!myShowBusyIndicator.asBoolean() || !canYield()) return;
218 if (myTree instanceof com.intellij.ui.treeStructure.Tree) {
219 final com.intellij.ui.treeStructure.Tree tree = (Tree)myTree;
220 final boolean isBusy = !isReady() || forcedBusy;
221 if (isBusy && tree.isShowing()) {
222 tree.setPaintBusy(true);
223 myBusyAlarm.cancelAllRequests();
224 myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger());
227 tree.setPaintBusy(false);
232 private void initClearanceServiceIfNeeded() {
233 if (ourClearanceService != null) return;
235 ourClearanceService = ConcurrencyUtil.newSingleScheduledThreadExecutor("AbstractTreeBuilder's janitor", Thread.MIN_PRIORITY + 1);
236 ourClearanceService.scheduleWithFixedDelay(new Runnable() {
240 }, myJanitorPollPeriod, myJanitorPollPeriod, TimeUnit.MILLISECONDS);
243 private void cleanUpAll() {
244 final long now = System.currentTimeMillis();
245 final AbstractTreeUi[] uis = ourUi2Countdown.keySet().toArray(new AbstractTreeUi[ourUi2Countdown.size()]);
246 for (AbstractTreeUi eachUi : uis) {
247 if (eachUi == null) continue;
248 final Long timeToCleanup = ourUi2Countdown.get(eachUi);
249 if (timeToCleanup == null) continue;
250 if (now >= timeToCleanup.longValue()) {
251 ourUi2Countdown.remove(eachUi);
252 Runnable runnable = new Runnable() {
254 getBuilder().cleanUp();
257 if (isPassthroughMode()) {
260 UIUtil.invokeAndWaitIfNeeded(runnable);
266 protected void doCleanUp() {
267 Runnable cleanup = new Runnable() {
275 if (isPassthroughMode()) {
278 UIUtil.invokeLaterIfNeeded(cleanup);
282 private void disposeClearanceService() {
284 if (ourClearanceService != null) {
285 ourClearanceService.shutdown();
286 ourClearanceService = null;
289 catch (AccessControlException e) {
294 public void activate(boolean byShowing) {
295 myCanProcessDeferredSelections = true;
296 ourUi2Countdown.remove(this);
298 if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) {
299 getBuilder().updateFromRoot();
302 getUpdater().showNotify();
304 myWasEverShown |= byShowing;
307 public void deactivate() {
308 getUpdater().hideNotify();
309 myBusyAlarm.cancelAllRequests();
311 if (!myWasEverShown) return;
313 if (isNodeActionsPending()) {
314 cancelBackgroundLoading();
315 myUpdateFromRootRequested = true;
318 if (getClearOnHideDelay() >= 0) {
319 ourUi2Countdown.put(this, System.currentTimeMillis() + getClearOnHideDelay());
320 initClearanceServiceIfNeeded();
325 public void release() {
326 if (isReleased()) return;
328 myTree.removeTreeExpansionListener(myExpansionListener);
329 myTree.removeTreeSelectionListener(mySelectionListener);
330 myTree.removeFocusListener(myFocusListener);
332 disposeNode(getRootNode());
333 myElementToNodeMap.clear();
334 getUpdater().cancelAllRequests();
335 if (myWorker != null) {
336 myWorker.dispose(true);
339 TREE_NODE_WRAPPER.setValue(null);
340 if (myProgress != null) {
343 disposeClearanceService();
348 //todo [kirillk] afraid to do so just in release day, to uncomment
349 // myTreeStructure = null;
354 myDeferredSelections.clear();
355 myDeferredExpansions.clear();
356 myYeildingDoneRunnables.clear();
359 public boolean isReleased() {
360 return myBuilder == null;
363 protected void doExpandNodeChildren(final DefaultMutableTreeNode node) {
364 if (!myUnbuiltNodes.contains(node)) return;
365 if (isLoadedInBackground(getElementFor(node))) return;
367 getTreeStructure().commit();
368 addSubtreeToUpdate(node);
369 getUpdater().performUpdate();
372 public final AbstractTreeStructure getTreeStructure() {
373 return myTreeStructure;
376 public final JTree getTree() {
381 private NodeDescriptor getDescriptorFrom(DefaultMutableTreeNode node) {
382 return (NodeDescriptor)node.getUserObject();
386 public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) {
387 DefaultMutableTreeNode result = null;
388 if (validateAgainstStructure) {
391 final DefaultMutableTreeNode node = findNode(element, index);
392 if (node == null) break;
394 if (isNodeValidForElement(element, node)) {
403 result = getFirstNode(element);
407 if (result != null && !isNodeInStructure(result)) {
415 private boolean isNodeInStructure(DefaultMutableTreeNode node) {
416 return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot();
419 private boolean isNodeValidForElement(final Object element, final DefaultMutableTreeNode node) {
420 return isSameHierarchy(element, node) || isValidChildOfParent(element, node);
423 private boolean isValidChildOfParent(final Object element, final DefaultMutableTreeNode node) {
424 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
425 final Object parentElement = getElementFor(parent);
426 if (!isInStructure(parentElement)) return false;
428 if (parent instanceof ElementNode) {
429 return ((ElementNode)parent).isValidChild(element);
432 for (int i = 0; i < parent.getChildCount(); i++) {
433 final TreeNode child = parent.getChildAt(i);
434 final Object eachElement = getElementFor(child);
435 if (element.equals(eachElement)) return true;
442 private boolean isSameHierarchy(Object eachParent, DefaultMutableTreeNode eachParentNode) {
443 boolean valid = true;
445 if (eachParent == null) {
446 valid = eachParentNode == null;
450 if (!eachParent.equals(getElementFor(eachParentNode))) {
455 eachParent = getTreeStructure().getParentElement(eachParent);
456 eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent();
461 public final DefaultMutableTreeNode getNodeForPath(Object[] path) {
462 DefaultMutableTreeNode node = null;
463 for (final Object pathElement : path) {
464 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
472 public final void buildNodeForElement(Object element) {
473 getUpdater().performUpdate();
474 DefaultMutableTreeNode node = getNodeForElement(element, false);
476 final java.util.List<Object> elements = new ArrayList<Object>();
478 element = getTreeStructure().getParentElement(element);
479 if (element == null) {
482 elements.add(0, element);
485 for (final Object element1 : elements) {
486 node = getNodeForElement(element1, false);
494 public final void buildNodeForPath(Object[] path) {
495 getUpdater().performUpdate();
496 DefaultMutableTreeNode node = null;
497 for (final Object pathElement : path) {
498 node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement);
499 if (node != null && node != path[path.length - 1]) {
505 public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) {
506 myNodeDescriptorComparator = nodeDescriptorComparator;
507 myLastComparatorStamp = -1;
508 getBuilder().queueUpdateFrom(getTreeStructure().getRootElement(), true);
511 protected AbstractTreeBuilder getBuilder() {
515 protected final void initRootNode() {
516 if (myUpdateIfInactive) {
520 myUpdateFromRootRequested = true;
524 private void initRootNodeNowIfNeeded(final TreeUpdatePass pass) {
525 if (myRootNodeWasInitialized) return;
527 myRootNodeWasInitialized = true;
529 final Object rootElement = getTreeStructure().getRootElement();
530 addNodeAction(rootElement, new NodeAction() {
531 public void onReady(final DefaultMutableTreeNode node) {
532 processDeferredActions();
537 final Ref<NodeDescriptor> rootDescriptor = new Ref<NodeDescriptor>(null);
538 final boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(rootElement);
540 Runnable build = new Runnable() {
542 rootDescriptor.set(getTreeStructure().createDescriptor(rootElement, null));
543 getRootNode().setUserObject(rootDescriptor.get());
544 update(rootDescriptor.get(), true);
549 Runnable update = new Runnable() {
551 if (getElementFromDescriptor(rootDescriptor.get()) != null) {
552 createMapping(getElementFromDescriptor(rootDescriptor.get()), getRootNode());
556 insertLoadingNode(getRootNode(), true);
558 boolean willUpdate = false;
559 if (isAutoExpand(rootDescriptor.get())) {
560 willUpdate = myUnbuiltNodes.contains(getRootNode());
561 expand(getRootNode(), true);
564 updateNodeChildren(getRootNode(), pass, null, false, false, false, true);
566 if (getRootNode().getChildCount() == 0) {
567 myTreeModel.nodeChanged(getRootNode());
573 queueToBackground(build, update, null);
581 private boolean isAutoExpand(NodeDescriptor descriptor) {
582 boolean autoExpand = false;
584 if (descriptor != null) {
585 autoExpand = getBuilder().isAutoExpandNode(descriptor);
588 Object element = getElementFromDescriptor(descriptor);
589 autoExpand = validateAutoExpand(autoExpand, element);
591 if (!autoExpand && !myTree.isRootVisible()) {
592 if (element != null && element.equals(getTreeStructure().getRootElement())) return true;
598 private boolean validateAutoExpand(boolean autoExpand, Object element) {
600 int distance = getDistanceToAutoExpandRoot(element);
602 myAutoExpandRoots.add(element);
604 if (distance >= myAutoExpandDepth.asInteger() - 1) {
612 private int getDistanceToAutoExpandRoot(Object element) {
615 Object eachParent = element;
616 while (eachParent != null) {
617 if (myAutoExpandRoots.contains(eachParent)) break;
618 eachParent = getTreeStructure().getParentElement(eachParent);
622 return eachParent != null ? distance : -1;
625 private boolean isAutoExpand(DefaultMutableTreeNode node) {
626 return isAutoExpand(getDescriptorFrom(node));
629 private AsyncResult<Boolean> update(final NodeDescriptor nodeDescriptor, boolean now) {
630 final AsyncResult<Boolean> result = new AsyncResult<Boolean>();
632 if (now || isPassthroughMode()) {
633 return new AsyncResult<Boolean>().setDone(_update(nodeDescriptor));
636 Object element = getElementFromDescriptor(nodeDescriptor);
637 boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(element);
639 boolean edt = isEdt();
642 final Ref<Boolean> changes = new Ref<Boolean>(false);
643 queueToBackground(new Runnable() {
645 changes.set(_update(nodeDescriptor));
649 result.setDone(changes.get());
654 result.setDone(_update(nodeDescriptor));
658 if (edt || !myWasEverShown) {
659 result.setDone(_update(nodeDescriptor));
662 UIUtil.invokeLaterIfNeeded(new Runnable() {
665 result.setDone(_update(nodeDescriptor));
668 result.setRejected();
675 result.doWhenDone(new AsyncResult.Handler<Boolean>() {
676 public void run(Boolean changes) {
678 final long updateStamp = nodeDescriptor.getUpdateCount();
679 UIUtil.invokeLaterIfNeeded(new Runnable() {
681 Object element = nodeDescriptor.getElement();
682 DefaultMutableTreeNode node = getNodeForElement(element, false);
684 TreePath path = getPathFor(node);
685 if (path != null && myTree.isVisible(path)) {
686 updateNodeImageAndPosition(node, false);
699 private boolean _update(NodeDescriptor nodeDescriptor) {
700 nodeDescriptor.setUpdateCount(nodeDescriptor.getUpdateCount() + 1);
701 return getBuilder().updateNodeDescriptor(nodeDescriptor);
704 private void assertIsDispatchThread() {
705 if (isPassthroughMode()) return;
707 if (isTreeShowing() && !isEdt()) {
708 LOG.error("Must be in event-dispatch thread");
712 private boolean isEdt() {
713 return SwingUtilities.isEventDispatchThread();
716 private boolean isTreeShowing() {
720 private void assertNotDispatchThread() {
721 if (isPassthroughMode()) return;
724 LOG.error("Must not be in event-dispatch thread");
728 private void processDeferredActions() {
729 processDeferredActions(myDeferredSelections);
730 processDeferredActions(myDeferredExpansions);
733 private void processDeferredActions(Set<Runnable> actions) {
734 final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]);
736 for (Runnable runnable : runnables) {
741 //todo: to make real callback
742 public ActionCallback queueUpdate(Object element) {
743 AbstractTreeUpdater updater = getUpdater();
744 if (updater == null) {
745 return new ActionCallback.Rejected();
748 final ActionCallback result = new ActionCallback();
749 DefaultMutableTreeNode node = getNodeForElement(element, false);
751 addSubtreeToUpdate(node);
754 addSubtreeToUpdate(getRootNode());
757 updater.runAfterUpdate(new Runnable() {
765 public void doUpdateFromRoot() {
766 updateSubtree(getRootNode(), false);
769 public ActionCallback doUpdateFromRootCB() {
770 final ActionCallback cb = new ActionCallback();
771 getUpdater().runAfterUpdate(new Runnable() {
776 updateSubtree(getRootNode(), false);
780 public final void updateSubtree(DefaultMutableTreeNode node, boolean canSmartExpand) {
781 updateSubtree(new TreeUpdatePass(node), canSmartExpand);
784 public final void updateSubtree(TreeUpdatePass pass, boolean canSmartExpand) {
785 if (getUpdater() != null) {
786 getUpdater().addSubtreeToUpdate(pass);
789 updateSubtreeNow(pass, canSmartExpand);
793 final void updateSubtreeNow(TreeUpdatePass pass, boolean canSmartExpand) {
794 maybeSetBusyAndScheduleWaiterForReady(true);
796 initRootNodeNowIfNeeded(pass);
798 final DefaultMutableTreeNode node = pass.getNode();
800 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
802 setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate();
804 boolean forceUpdate = true;
805 TreePath path = getPathFor(node);
806 boolean invisible = !myTree.isExpanded(path) && (path.getParentPath() == null || !myTree.isExpanded(path.getParentPath()));
808 if (invisible && myUnbuiltNodes.contains(node)) {
812 updateNodeChildren(node, pass, null, false, canSmartExpand, forceUpdate, false);
815 private boolean isToBuildInBackground(NodeDescriptor descriptor) {
816 return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor));
820 UpdaterTreeState setUpdaterState(UpdaterTreeState state) {
821 final UpdaterTreeState oldState = myUpdaterState;
822 if (oldState == null) {
823 myUpdaterState = state;
827 oldState.addAll(state);
832 protected void doUpdateNode(final DefaultMutableTreeNode node) {
833 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
834 final NodeDescriptor descriptor = getDescriptorFrom(node);
835 final Object prevElement = getElementFromDescriptor(descriptor);
836 if (prevElement == null) return;
837 update(descriptor, false).doWhenDone(new AsyncResult.Handler<Boolean>() {
838 public void run(Boolean changes) {
839 if (!isValid(descriptor)) {
840 if (isInStructure(prevElement)) {
841 getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement));
846 updateNodeImageAndPosition(node, true);
852 public Object getElementFromDescriptor(NodeDescriptor descriptor) {
853 return getBuilder().getTreeStructureElement(descriptor);
856 private void updateNodeChildren(final DefaultMutableTreeNode node,
857 final TreeUpdatePass pass,
858 @Nullable LoadedChildren loadedChildren,
860 final boolean toSmartExpand,
862 final boolean descriptorIsUpToDate) {
864 getTreeStructure().commit();
867 final NodeDescriptor descriptor = getDescriptorFrom(node);
868 if (descriptor == null) {
869 removeLoading(node, true);
873 final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node);
874 final boolean wasLeaf = node.getChildCount() == 0;
877 boolean bgBuild = isToBuildInBackground(descriptor);
878 boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded;
880 if (notRequiredToUpdateChildren && forceUpdate && !wasExpanded) {
881 boolean alwaysPlus = getBuilder().isAlwaysShowPlus(descriptor);
882 if (alwaysPlus && wasLeaf) {
883 notRequiredToUpdateChildren = false;
885 notRequiredToUpdateChildren = alwaysPlus;
889 final Ref<LoadedChildren> preloaded = new Ref<LoadedChildren>(loadedChildren);
890 boolean descriptorWasUpdated = descriptorIsUpToDate;
892 if (notRequiredToUpdateChildren) {
893 if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) {
894 insertLoadingNode(node, true);
901 if (myUnbuiltNodes.contains(node)) {
902 if (!descriptorWasUpdated) {
903 update(descriptor, true);
904 descriptorWasUpdated = true;
907 if (processAlwaysLeaf(node)) return;
909 Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null);
910 if (unbuilt.getFirst()) return;
911 preloaded.set(unbuilt.getSecond());
917 final boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded);
919 if (!forcedNow && isToBuildInBackground(descriptor)) {
920 if (processAlwaysLeaf(node)) return;
922 queueBackgroundUpdate(
923 new UpdateInfo(descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorWasUpdated), node);
927 if (!descriptorWasUpdated) {
928 update(descriptor, false).doWhenDone(new Runnable() {
930 if (processAlwaysLeaf(node)) return;
931 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
936 if (processAlwaysLeaf(node)) return;
938 updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, wasLeaf, childForceUpdate);
943 processNodeActionsIfReady(node);
947 private boolean processAlwaysLeaf(DefaultMutableTreeNode node) {
948 Object element = getElementFor(node);
949 NodeDescriptor desc = getDescriptorFrom(node);
951 if (desc == null) return false;
953 if (getTreeStructure().isAlwaysLeaf(element)) {
954 removeLoading(node, true);
956 if (node.getChildCount() > 0) {
957 final TreeNode[] children = new TreeNode[node.getChildCount()];
958 for (int i = 0; i < node.getChildCount(); i++) {
959 children[i] = node.getChildAt(i);
962 if (isSelectionInside(node)) {
963 addSelectionPath(getPathFor(node), true, Condition.TRUE, null);
966 processInnerChange(new Runnable() {
968 for (TreeNode each : children) {
969 removeNodeFromParent((MutableTreeNode)each, true);
970 disposeNode((DefaultMutableTreeNode)each);
976 removeFromUnbuilt(node);
977 desc.setWasDeclaredAlwaysLeaf(true);
978 processNodeActionsIfReady(node);
981 boolean wasLeaf = desc.isWasDeclaredAlwaysLeaf();
982 desc.setWasDeclaredAlwaysLeaf(false);
985 insertLoadingNode(node, true);
992 private boolean isChildNodeForceUpdate(DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) {
993 TreePath path = getPathFor(node);
994 return parentForceUpdate && (parentExpanded || myTree.isExpanded(path));
997 private void updateNodeChildrenNow(final DefaultMutableTreeNode node,
998 final TreeUpdatePass pass,
999 final LoadedChildren preloadedChildren,
1000 final boolean toSmartExpand,
1001 final boolean wasExpanded,
1002 final boolean wasLeaf,
1003 final boolean forceUpdate) {
1004 final NodeDescriptor descriptor = getDescriptorFrom(node);
1006 final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren);
1007 final LoadedChildren loadedChildren =
1008 preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray());
1011 addToUpdating(node);
1012 pass.setCurrentNode(node);
1014 final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
1016 processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren)
1017 .doWhenDone(new Runnable() {
1019 if (isDisposed(node)) {
1023 removeLoading(node, false);
1025 final boolean expanded = isExpanded(node, wasExpanded);
1027 collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren)
1028 .doWhenDone(new AsyncResult.Handler<ArrayList<TreeNode>>() {
1029 public void run(ArrayList<TreeNode> nodesToInsert) {
1030 insertNodesInto(nodesToInsert, node);
1031 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
1032 removeLoading(node, true);
1033 removeFromUpdating(node);
1035 if (node.getChildCount() > 0) {
1037 expand(node, canSmartExpand);
1041 final Object element = getElementFor(node);
1042 addNodeAction(element, new NodeAction() {
1043 public void onReady(final DefaultMutableTreeNode node) {
1044 removeLoading(node, false);
1048 processNodeActionsIfReady(node);
1055 private boolean isDisposed(DefaultMutableTreeNode node) {
1056 return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot());
1059 private void expand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1060 expand(new TreePath(node.getPath()), canSmartExpand);
1063 private void expand(final TreePath path, boolean canSmartExpand) {
1064 if (path == null) return;
1067 final Object last = path.getLastPathComponent();
1068 boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent());
1069 final boolean isRoot = last == myTree.getModel().getRoot();
1070 final TreePath parent = path.getParentPath();
1071 if (isRoot && !myTree.isExpanded(path)) {
1072 if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) {
1073 insertLoadingNode((DefaultMutableTreeNode)last, false);
1075 expandPath(path, canSmartExpand);
1077 else if (myTree.isExpanded(path) || (isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last))) {
1078 if (last instanceof DefaultMutableTreeNode) {
1079 processNodeActionsIfReady((DefaultMutableTreeNode)last);
1083 if (isLeaf && myUnbuiltNodes.contains(last)) {
1084 insertLoadingNode((DefaultMutableTreeNode)last, true);
1085 expandPath(path, canSmartExpand);
1087 else if (isLeaf && parent != null) {
1088 final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent();
1089 if (parentNode != null) {
1090 addToUnbuilt(parentNode);
1092 expandPath(parent, canSmartExpand);
1095 expandPath(path, canSmartExpand);
1100 private void addToUnbuilt(DefaultMutableTreeNode node) {
1101 myUnbuiltNodes.add(node);
1104 private void removeFromUnbuilt(DefaultMutableTreeNode node) {
1105 myUnbuiltNodes.remove(node);
1108 private Pair<Boolean, LoadedChildren> processUnbuilt(final DefaultMutableTreeNode node,
1109 final NodeDescriptor descriptor,
1110 final TreeUpdatePass pass,
1112 final LoadedChildren loadedChildren) {
1113 if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) {
1114 return new Pair<Boolean, LoadedChildren>(true, null);
1117 final Object element = getElementFor(node);
1119 final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element));
1123 if (children.getElements().size() == 0) {
1124 removeLoading(node, true);
1128 if (isAutoExpand(node)) {
1129 addNodeAction(getElementFor(node), new NodeAction() {
1130 public void onReady(final DefaultMutableTreeNode node) {
1131 final TreePath path = new TreePath(node.getPath());
1132 if (getTree().isExpanded(path) || children.getElements().size() == 0) {
1133 removeLoading(node, false);
1136 maybeYeild(new ActiveRunnable() {
1137 public ActionCallback run() {
1138 expand(element, null);
1139 return new ActionCallback.Done();
1149 processNodeActionsIfReady(node);
1151 return new Pair<Boolean, LoadedChildren>(processed, children);
1154 private boolean removeIfLoading(TreeNode node) {
1155 if (isLoadingNode(node)) {
1156 moveSelectionToParentIfNeeded(node);
1157 removeNodeFromParent((MutableTreeNode)node, false);
1164 private void moveSelectionToParentIfNeeded(TreeNode node) {
1165 TreePath path = getPathFor(node);
1166 if (myTree.getSelectionModel().isPathSelected(path)) {
1167 TreePath parentPath = path.getParentPath();
1168 myTree.getSelectionModel().removeSelectionPath(path);
1169 if (parentPath != null) {
1170 myTree.getSelectionModel().addSelectionPath(parentPath);
1175 //todo [kirillk] temporary consistency check
1176 private Object[] getChildrenFor(final Object element) {
1177 final Object[] passOne;
1179 passOne = getTreeStructure().getChildElements(element);
1181 catch (IndexNotReadyException e) {
1182 if (!myWasEverIndexNotReady) {
1183 myWasEverIndexNotReady = true;
1184 LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure());
1186 return ArrayUtil.EMPTY_OBJECT_ARRAY;
1189 if (!myCheckStructure) return passOne;
1191 final Object[] passTwo = getTreeStructure().getChildElements(element);
1193 final HashSet two = new HashSet(Arrays.asList(passTwo));
1195 if (passOne.length != passTwo.length) {
1197 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1201 for (Object eachInOne : passOne) {
1202 if (!two.contains(eachInOne)) {
1204 "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" +
1214 private void updateNodesToInsert(final ArrayList<TreeNode> nodesToInsert,
1215 TreeUpdatePass pass,
1216 boolean canSmartExpand,
1217 boolean forceUpdate) {
1218 for (TreeNode aNodesToInsert : nodesToInsert) {
1219 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)aNodesToInsert;
1220 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
1224 private ActionCallback processExistingNodes(final DefaultMutableTreeNode node,
1225 final MutualMap<Object, Integer> elementToIndexMap,
1226 final TreeUpdatePass pass,
1227 final boolean canSmartExpand,
1228 final boolean forceUpdate,
1229 final boolean wasExpaned,
1230 final LoadedChildren preloaded) {
1232 final ArrayList<TreeNode> childNodes = TreeUtil.childrenToArray(node);
1233 return maybeYeild(new ActiveRunnable() {
1234 public ActionCallback run() {
1235 if (pass.isExpired()) return new ActionCallback.Rejected();
1236 if (childNodes.size() == 0) return new ActionCallback.Done();
1239 final ActionCallback result = new ActionCallback(childNodes.size());
1241 for (TreeNode each : childNodes) {
1242 final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each;
1243 if (isLoadingNode(eachChild)) {
1248 final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned);
1250 maybeYeild(new ActiveRunnable() {
1252 public ActionCallback run() {
1253 return processExistingNode(eachChild, getDescriptorFrom(eachChild), node, elementToIndexMap, pass, canSmartExpand,
1254 childForceUpdate, preloaded);
1256 }, pass, node).notify(result);
1258 if (result.isRejected()) {
1268 private boolean isRerunNeeded(TreeUpdatePass pass) {
1269 if (pass.isExpired()) return false;
1271 final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode();
1273 return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass);
1276 private ActionCallback maybeYeild(final ActiveRunnable processRunnable, final TreeUpdatePass pass, final DefaultMutableTreeNode node) {
1277 final ActionCallback result = new ActionCallback();
1279 if (isRerunNeeded(pass)) {
1280 getUpdater().addSubtreeToUpdate(pass);
1281 result.setRejected();
1284 if (isToYieldUpdateFor(node)) {
1285 pass.setCurrentNode(node);
1286 yieldAndRun(new Runnable() {
1288 if (pass.isExpired()) return;
1290 if (isRerunNeeded(pass)) {
1291 runDone(new Runnable() {
1293 if (!pass.isExpired()) {
1294 getUpdater().addSubtreeToUpdate(pass);
1298 result.setRejected();
1301 processRunnable.run().notify(result);
1307 processRunnable.run().notify(result);
1314 private void yieldAndRun(final Runnable runnable, final TreeUpdatePass pass) {
1315 myYeildingPasses.add(pass);
1316 myYeildingNow = true;
1317 yield(new Runnable() {
1323 runOnYieldingDone(new Runnable() {
1328 executeYieldingRequest(runnable, pass);
1335 public boolean isYeildingNow() {
1336 return myYeildingNow;
1339 private boolean hasSheduledUpdates() {
1340 return getUpdater().hasNodesToUpdate() || isLoadingInBackgroundNow();
1343 public boolean isReady() {
1344 return isIdle() && !hasPendingWork() && !isNodeActionsPending();
1347 public boolean hasPendingWork() {
1348 return hasNodesToUpdate() || (myUpdaterState != null && myUpdaterState.isProcessingNow());
1351 public boolean isIdle() {
1352 return !isYeildingNow() && !isWorkerBusy() && (!hasSheduledUpdates() || getUpdater().isInPostponeMode());
1355 private void executeYieldingRequest(Runnable runnable, TreeUpdatePass pass) {
1357 myYeildingPasses.remove(pass);
1361 maybeYeildingFinished();
1365 private void maybeYeildingFinished() {
1366 if (myYeildingPasses.size() == 0) {
1367 myYeildingNow = false;
1368 flushPendingNodeActions();
1373 if (isReleased()) return;
1376 if (myTree.isShowing() || myUpdateIfInactive) {
1377 myInitialized.setDone();
1380 if (myTree.isShowing()) {
1381 if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) {
1382 TreeUtil.ensureSelection(myTree);
1386 if (myInitialized.isDone()) {
1387 for (ActionCallback each : getReadyCallbacks(true)) {
1394 private void flushPendingNodeActions() {
1395 final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]);
1396 myPendingNodeActions.clear();
1398 for (DefaultMutableTreeNode each : nodes) {
1399 processNodeActionsIfReady(each);
1402 final Runnable[] actions = myYeildingDoneRunnables.toArray(new Runnable[myYeildingDoneRunnables.size()]);
1403 for (Runnable each : actions) {
1404 if (!isYeildingNow()) {
1405 myYeildingDoneRunnables.remove(each);
1413 protected void runOnYieldingDone(Runnable onDone) {
1414 getBuilder().runOnYeildingDone(onDone);
1417 protected void yield(Runnable runnable) {
1418 getBuilder().yield(runnable);
1421 private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) {
1422 if (!canYield()) return false;
1423 return getBuilder().isToYieldUpdateFor(node);
1426 private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor,
1427 @Nullable LoadedChildren preloadedChildren) {
1428 MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true);
1429 List children = preloadedChildren != null
1430 ? preloadedChildren.getElements()
1431 : Arrays.asList(getChildrenFor(getBuilder().getTreeStructureElement(descriptor)));
1433 for (Object child : children) {
1434 if (!isValid(child)) continue;
1435 elementToIndexMap.put(child, Integer.valueOf(index));
1438 return elementToIndexMap;
1441 private void expand(final DefaultMutableTreeNode node,
1442 final NodeDescriptor descriptor,
1443 final boolean wasLeaf,
1444 final boolean canSmartExpand) {
1445 final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1446 alarm.addRequest(new Runnable() {
1448 myTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1450 }, WAIT_CURSOR_DELAY);
1452 if (wasLeaf && isAutoExpand(descriptor)) {
1453 expand(node, canSmartExpand);
1456 ArrayList<TreeNode> nodes = TreeUtil.childrenToArray(node);
1457 for (TreeNode node1 : nodes) {
1458 final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node1;
1459 if (isLoadingNode(childNode)) continue;
1460 NodeDescriptor childDescr = getDescriptorFrom(childNode);
1461 if (isAutoExpand(childDescr)) {
1462 addNodeAction(getElementFor(childNode), new NodeAction() {
1463 public void onReady(DefaultMutableTreeNode node) {
1464 expand(childNode, canSmartExpand);
1467 addSubtreeToUpdate(childNode);
1471 int n = alarm.cancelAllRequests();
1473 myTree.setCursor(Cursor.getDefaultCursor());
1477 public static boolean isLoadingNode(final Object node) {
1478 return node instanceof LoadingNode;
1481 private AsyncResult<ArrayList<TreeNode>> collectNodesToInsert(final NodeDescriptor descriptor,
1482 final MutualMap<Object, Integer> elementToIndexMap,
1483 final DefaultMutableTreeNode parent,
1484 final boolean addLoadingNode,
1485 @NotNull final LoadedChildren loadedChildren) {
1486 final AsyncResult<ArrayList<TreeNode>> result = new AsyncResult<ArrayList<TreeNode>>();
1488 final ArrayList<TreeNode> nodesToInsert = new ArrayList<TreeNode>();
1489 final Collection<Object> allElements = elementToIndexMap.getKeys();
1491 final ActionCallback processingDone = new ActionCallback(allElements.size());
1493 for (final Object child : allElements) {
1494 Integer index = elementToIndexMap.getValue(child);
1495 final Ref<NodeDescriptor> childDescr = new Ref<NodeDescriptor>(loadedChildren.getDescriptor(child));
1496 boolean needToUpdate = false;
1497 if (childDescr.get() == null) {
1498 childDescr.set(getTreeStructure().createDescriptor(child, descriptor));
1499 needToUpdate = true;
1502 //noinspection ConstantConditions
1503 if (childDescr.get() == null) {
1504 LOG.error("childDescr == null, treeStructure = " + getTreeStructure() + ", child = " + child);
1505 processingDone.setDone();
1508 childDescr.get().setIndex(index.intValue());
1510 final ActionCallback update = new ActionCallback();
1512 update(childDescr.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
1513 public void run(Boolean changes) {
1514 loadedChildren.putDescriptor(child, childDescr.get(), changes);
1523 update.doWhenDone(new Runnable() {
1525 Object element = getElementFromDescriptor(childDescr.get());
1526 if (element == null) {
1527 processingDone.setDone();
1530 DefaultMutableTreeNode node = getNodeForElement(element, false);
1531 if (node == null || node.getParent() != parent) {
1532 final DefaultMutableTreeNode childNode = createChildNode(childDescr.get());
1533 if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr.get())) {
1534 insertLoadingNode(childNode, true);
1537 addToUnbuilt(childNode);
1539 nodesToInsert.add(childNode);
1540 createMapping(element, childNode);
1542 processingDone.setDone();
1548 processingDone.doWhenDone(new Runnable() {
1550 result.setDone(nodesToInsert);
1557 protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) {
1558 return new ElementNode(this, descriptor);
1561 protected boolean canYield() {
1562 return myCanYield && myYeildingUpdate.asBoolean();
1565 public long getClearOnHideDelay() {
1566 return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime");
1569 public ActionCallback getInitialized() {
1570 return myInitialized;
1573 public ActionCallback getReady(Object requestor) {
1575 return new ActionCallback.Done();
1578 return addReadyCallback(requestor);
1582 private void addToUpdating(DefaultMutableTreeNode node) {
1583 synchronized (myUpdatingChildren) {
1584 myUpdatingChildren.add(node);
1588 private void removeFromUpdating(DefaultMutableTreeNode node) {
1589 synchronized (myUpdatingChildren) {
1590 myUpdatingChildren.remove(node);
1594 public boolean isUpdatingNow(DefaultMutableTreeNode node) {
1595 synchronized (myUpdatingChildren) {
1596 return myUpdatingChildren.contains(node);
1600 boolean hasUpdatingNow() {
1601 synchronized (myUpdatingChildren) {
1602 return myUpdatingChildren.size() > 0;
1606 public Map getNodeActions() {
1607 return myNodeActions;
1610 public List<Object> getLoadedChildrenFor(Object element) {
1611 List<Object> result = new ArrayList<Object>();
1613 DefaultMutableTreeNode node = (DefaultMutableTreeNode)getNodeForElement(element, false);
1615 for (int i = 0; i < node.getChildCount(); i++) {
1616 TreeNode each = node.getChildAt(i);
1617 if (isLoadingNode(each)) continue;
1619 result.add(getElementFor(each));
1626 public boolean hasNodesToUpdate() {
1627 return getUpdater().hasNodesToUpdate() || hasUpdatingNow() || isLoadingInBackgroundNow();
1630 public List<Object> getExpandedElements() {
1631 List<Object> result = new ArrayList<Object>();
1632 Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode()));
1633 while (enumeration.hasMoreElements()) {
1634 TreePath each = enumeration.nextElement();
1635 Object eachElement = getElementFor(each.getLastPathComponent());
1636 if (eachElement != null) {
1637 result.add(eachElement);
1644 static class ElementNode extends DefaultMutableTreeNode {
1646 Set<Object> myElements = new HashSet<Object>();
1647 AbstractTreeUi myUi;
1649 ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) {
1655 public void insert(final MutableTreeNode newChild, final int childIndex) {
1656 super.insert(newChild, childIndex);
1657 final Object element = myUi.getElementFor(newChild);
1658 if (element != null) {
1659 myElements.add(element);
1664 public void remove(final int childIndex) {
1665 final TreeNode node = getChildAt(childIndex);
1666 super.remove(childIndex);
1667 final Object element = myUi.getElementFor(node);
1668 if (element != null) {
1669 myElements.remove(element);
1673 boolean isValidChild(Object childElement) {
1674 return myElements.contains(childElement);
1678 public String toString() {
1679 return String.valueOf(getUserObject());
1683 private boolean isUpdatingParent(DefaultMutableTreeNode kid) {
1684 return getUpdatingParent(kid) != null;
1687 private DefaultMutableTreeNode getUpdatingParent(DefaultMutableTreeNode kid) {
1688 DefaultMutableTreeNode eachParent = kid;
1689 while (eachParent != null) {
1690 if (isUpdatingNow(eachParent)) return eachParent;
1691 eachParent = (DefaultMutableTreeNode)eachParent.getParent();
1697 private boolean isLoadedInBackground(Object element) {
1698 return getLoadedInBackground(element) != null;
1701 private UpdateInfo getLoadedInBackground(Object element) {
1702 synchronized (myLoadedInBackground) {
1703 return myLoadedInBackground.get(element);
1707 private void addToLoadedInBackground(Object element, UpdateInfo info) {
1708 synchronized (myLoadedInBackground) {
1709 myLoadedInBackground.put(element, info);
1713 private void removeFromLoadedInBackground(final Object element) {
1714 synchronized (myLoadedInBackground) {
1715 myLoadedInBackground.remove(element);
1719 private boolean isLoadingInBackgroundNow() {
1720 synchronized (myLoadedInBackground) {
1721 return myLoadedInBackground.size() > 0;
1725 private boolean queueBackgroundUpdate(final UpdateInfo updateInfo, final DefaultMutableTreeNode node) {
1726 assertIsDispatchThread();
1728 final Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor());
1730 UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor);
1731 if (loaded != null) {
1732 loaded.apply(updateInfo);
1736 addToLoadedInBackground(oldElementFromDescriptor, updateInfo);
1738 if (!isNodeBeingBuilt(node)) {
1739 LoadingNode loadingNode = new LoadingNode(getLoadingNodeText());
1740 myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
1743 final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
1744 final Ref<Object> elementFromDescriptor = new Ref<Object>();
1745 Runnable buildRunnable = new Runnable() {
1751 if (!updateInfo.isDescriptorIsUpToDate()) {
1752 update(updateInfo.getDescriptor(), true);
1755 Object element = getElementFromDescriptor(updateInfo.getDescriptor());
1756 if (element == null) {
1757 removeFromLoadedInBackground(oldElementFromDescriptor);
1761 elementFromDescriptor.set(element);
1763 Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(updateInfo.getDescriptor()));
1764 LoadedChildren loaded = new LoadedChildren(loadedElements);
1765 for (Object each : loadedElements) {
1766 NodeDescriptor eachChildDescriptor = getTreeStructure().createDescriptor(each, updateInfo.getDescriptor());
1767 loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).getResult());
1770 children.set(loaded);
1774 final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1];
1775 Runnable updateRunnable = new Runnable() {
1777 if (isReleased()) return;
1778 if (children.get() == null) return;
1780 if (isRerunNeeded(updateInfo.getPass())) {
1781 removeFromLoadedInBackground(elementFromDescriptor.get());
1782 getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1786 removeFromLoadedInBackground(elementFromDescriptor.get());
1788 if (myUnbuiltNodes.contains(node)) {
1789 Pair<Boolean, LoadedChildren> unbuilt =
1790 processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()),
1792 if (unbuilt.getFirst()) {
1793 nodeToProcessActions[0] = node;
1798 updateNodeChildren(node, updateInfo.getPass(), children.get(), true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(),
1802 if (isRerunNeeded(updateInfo.getPass())) {
1803 getUpdater().addSubtreeToUpdate(updateInfo.getPass());
1807 Object element = elementFromDescriptor.get();
1809 if (element != null) {
1810 removeLoading(node, true);
1811 nodeToProcessActions[0] = node;
1815 queueToBackground(buildRunnable, updateRunnable, new Runnable() {
1817 if (nodeToProcessActions[0] != null) {
1818 processNodeActionsIfReady(nodeToProcessActions[0]);
1825 private boolean isExpanded(DefaultMutableTreeNode node, boolean isExpanded) {
1826 return isExpanded || myTree.isExpanded(getPathFor(node));
1829 private void removeLoading(DefaultMutableTreeNode parent, boolean removeFromUnbuilt) {
1830 for (int i = 0; i < parent.getChildCount(); i++) {
1831 TreeNode child = parent.getChildAt(i);
1832 if (removeIfLoading(child)) {
1837 if (removeFromUnbuilt) {
1838 removeFromUnbuilt(parent);
1841 if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
1842 insertLoadingNode(parent, false);
1848 private void processNodeActionsIfReady(final DefaultMutableTreeNode node) {
1849 assertIsDispatchThread();
1851 if (isNodeBeingBuilt(node)) return;
1853 final Object o = node.getUserObject();
1854 if (!(o instanceof NodeDescriptor)) return;
1857 if (isYeildingNow()) {
1858 myPendingNodeActions.add(node);
1862 final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o);
1864 boolean childrenReady = !isLoadedInBackground(element);
1866 processActions(node, element, myNodeActions, childrenReady ? myNodeChildrenActions : null);
1867 if (childrenReady) {
1868 processActions(node, element, myNodeChildrenActions, null);
1871 if (!isUpdatingParent(node) && !isWorkerBusy()) {
1872 final UpdaterTreeState state = myUpdaterState;
1873 if (myNodeActions.size() == 0 && state != null && !state.isProcessingNow()) {
1874 if (!state.restore(childrenReady ? node : null)) {
1875 setUpdaterState(state);
1884 private void processActions(DefaultMutableTreeNode node, Object element, final Map<Object, List<NodeAction>> nodeActions, @Nullable final Map<Object, List<NodeAction>> secondaryNodeAction) {
1885 final List<NodeAction> actions = nodeActions.get(element);
1886 if (actions != null) {
1887 nodeActions.remove(element);
1889 List<NodeAction> secondary = secondaryNodeAction != null ? secondaryNodeAction.get(element) : null;
1890 for (NodeAction each : actions) {
1891 if (secondary != null && secondary.contains(each)) {
1892 secondary.remove(each);
1900 private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) {
1901 if (!getBuilder().isSmartExpand()) return false;
1903 boolean smartExpand = !myNotForSmartExpand.contains(node) && canSmartExpand;
1904 return smartExpand ? validateAutoExpand(smartExpand, getElementFor(node)) : false;
1907 private void processSmartExpand(final DefaultMutableTreeNode node, final boolean canSmartExpand, boolean forced) {
1908 if (!getBuilder().isSmartExpand()) return;
1910 boolean can = canSmartExpand(node, canSmartExpand);
1912 if (!can && !forced) return;
1914 if (isNodeBeingBuilt(node) && !forced) {
1915 addNodeAction(getElementFor(node), new NodeAction() {
1916 public void onReady(DefaultMutableTreeNode node) {
1917 processSmartExpand(node, canSmartExpand, true);
1922 TreeNode child = getChildForSmartExpand(node);
1923 if (child != null) {
1924 final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child);
1925 processInnerChange(new Runnable() {
1927 myTree.expandPath(childPath);
1935 private TreeNode getChildForSmartExpand(DefaultMutableTreeNode node) {
1936 int realChildCount = 0;
1937 TreeNode nodeToExpand = null;
1939 for (int i = 0; i < node.getChildCount(); i++) {
1940 TreeNode eachChild = node.getChildAt(i);
1942 if (!isLoadingNode(eachChild)) {
1944 if (nodeToExpand == null) {
1945 nodeToExpand = eachChild;
1949 if (realChildCount > 1) {
1950 nodeToExpand = null;
1955 return nodeToExpand;
1958 public boolean isLoadingChildrenFor(final Object nodeObject) {
1959 if (!(nodeObject instanceof DefaultMutableTreeNode)) return false;
1961 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1963 int loadingNodes = 0;
1964 for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) {
1965 TreeNode child = node.getChildAt(i);
1966 if (isLoadingNode(child)) {
1970 return loadingNodes > 0 && loadingNodes == node.getChildCount();
1973 private boolean isParentLoading(Object nodeObject) {
1974 return getParentLoading(nodeObject) != null;
1977 private DefaultMutableTreeNode getParentLoading(Object nodeObject) {
1978 if (!(nodeObject instanceof DefaultMutableTreeNode)) return null;
1980 DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject;
1982 TreeNode eachParent = node.getParent();
1984 while (eachParent != null) {
1985 eachParent = eachParent.getParent();
1986 if (eachParent instanceof DefaultMutableTreeNode) {
1987 final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent);
1988 if (isLoadedInBackground(eachElement)) return (DefaultMutableTreeNode)eachParent;
1995 protected String getLoadingNodeText() {
1996 return IdeBundle.message("progress.searching");
1999 private ActionCallback processExistingNode(final DefaultMutableTreeNode childNode,
2000 final NodeDescriptor childDescriptor,
2001 final DefaultMutableTreeNode parentNode,
2002 final MutualMap<Object, Integer> elementToIndexMap,
2003 final TreeUpdatePass pass,
2004 final boolean canSmartExpand,
2005 final boolean forceUpdate,
2006 LoadedChildren parentPreloadedChildren) {
2008 final ActionCallback result = new ActionCallback();
2010 if (pass.isExpired()) {
2011 return new ActionCallback.Rejected();
2014 final Ref<NodeDescriptor> childDesc = new Ref<NodeDescriptor>(childDescriptor);
2016 if (childDesc.get() == null) {
2018 return new ActionCallback.Rejected();
2020 final Object oldElement = getElementFromDescriptor(childDesc.get());
2021 if (oldElement == null) {
2023 return new ActionCallback.Rejected();
2026 AsyncResult<Boolean> update = new AsyncResult<Boolean>();
2027 if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) {
2028 update.setDone(parentPreloadedChildren.isUpdated(oldElement));
2031 update = update(childDesc.get(), false);
2034 update.doWhenDone(new AsyncResult.Handler<Boolean>() {
2035 public void run(Boolean isChanged) {
2036 final Ref<Boolean> changes = new Ref<Boolean>(isChanged);
2038 final Ref<Boolean> forceRemapping = new Ref<Boolean>(false);
2039 final Ref<Object> newElement = new Ref<Object>(getElementFromDescriptor(childDesc.get()));
2041 final Integer index = newElement.get() != null ? elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc.get())) : null;
2042 final AsyncResult<Boolean> updateIndexDone = new AsyncResult<Boolean>();
2043 final ActionCallback indexReady = new ActionCallback();
2044 if (index != null) {
2045 final Object elementFromMap = elementToIndexMap.getKey(index);
2046 if (elementFromMap != newElement.get() && elementFromMap.equals(newElement.get())) {
2047 if (isInStructure(elementFromMap) && isInStructure(newElement.get())) {
2048 if (parentNode.getUserObject() instanceof NodeDescriptor) {
2049 final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode);
2050 childDesc.set(getTreeStructure().createDescriptor(elementFromMap, parentDescriptor));
2051 childNode.setUserObject(childDesc.get());
2052 newElement.set(elementFromMap);
2053 forceRemapping.set(true);
2054 update(childDesc.get(), false).doWhenDone(new AsyncResult.Handler<Boolean>() {
2055 public void run(Boolean isChanged) {
2056 changes.set(isChanged);
2057 updateIndexDone.setDone(isChanged);
2063 updateIndexDone.setDone(changes.get());
2066 updateIndexDone.setDone(changes.get());
2069 updateIndexDone.doWhenDone(new Runnable() {
2071 if (childDesc.get().getIndex() != index.intValue()) {
2074 childDesc.get().setIndex(index.intValue());
2075 indexReady.setDone();
2080 updateIndexDone.setDone();
2083 updateIndexDone.doWhenDone(new Runnable() {
2085 if (index != null && changes.get()) {
2086 updateNodeImageAndPosition(childNode, false);
2088 if (!oldElement.equals(newElement.get()) | forceRemapping.get()) {
2089 removeMapping(oldElement, childNode, newElement.get());
2090 if (newElement.get() != null) {
2091 createMapping(newElement.get(), childNode);
2093 getDescriptorFrom(parentNode).setChildrenSortingStamp(-1);
2096 if (index == null) {
2097 int selectedIndex = -1;
2098 if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) {
2099 selectedIndex = parentNode.getIndex(childNode);
2102 if (childNode.getParent() instanceof DefaultMutableTreeNode) {
2103 final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent();
2104 if (myTree.isExpanded(new TreePath(parent.getPath()))) {
2105 if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) {
2106 insertLoadingNode(parent, false);
2111 Object disposedElement = getElementFor(childNode);
2113 removeNodeFromParent(childNode, selectedIndex >= 0);
2114 disposeNode(childNode);
2116 adjustSelectionOnChildRemove(parentNode, selectedIndex, disposedElement);
2119 elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc.get()));
2120 updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true);
2123 if (parentNode.equals(getRootNode())) {
2124 myTreeModel.nodeChanged(getRootNode());
2137 private void adjustSelectionOnChildRemove(DefaultMutableTreeNode parentNode, int selectedIndex, Object disposedElement) {
2138 DefaultMutableTreeNode node = getNodeForElement(disposedElement, false);
2139 if (node != null && isValidForSelectionAdjusting(node)) {
2140 Object newElement = getElementFor(node);
2141 addSelectionPath(getPathFor(node), true, getExpiredElementCondition(newElement), disposedElement);
2146 if (selectedIndex >= 0) {
2147 if (parentNode.getChildCount() > 0) {
2148 if (parentNode.getChildCount() > selectedIndex) {
2149 TreeNode newChildNode = parentNode.getChildAt(selectedIndex);
2150 if (isValidForSelectionAdjusting(newChildNode)) {
2151 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement), disposedElement);
2155 TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1);
2156 if (isValidForSelectionAdjusting(newChild)) {
2157 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement), disposedElement);
2162 addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement), disposedElement);
2167 private boolean isValidForSelectionAdjusting(TreeNode node) {
2168 if (!myTree.isRootVisible() && getRootNode() == node) return false;
2170 if (isLoadingNode(node)) return true;
2172 final Object elementInTree = getElementFor(node);
2173 if (elementInTree == null) return false;
2175 final TreeNode parentNode = node.getParent();
2176 final Object parentElementInTree = getElementFor(parentNode);
2177 if (parentElementInTree == null) return false;
2179 final Object parentElement = getTreeStructure().getParentElement(elementInTree);
2181 return parentElementInTree.equals(parentElement);
2184 public Condition getExpiredElementCondition(final Object element) {
2185 return new Condition() {
2186 public boolean value(final Object o) {
2187 return isInStructure(element);
2192 private void addSelectionPath(final TreePath path, final boolean isAdjustedSelection, final Condition isExpiredAdjustement, @Nullable final Object adjustmentCause) {
2193 processInnerChange(new Runnable() {
2195 TreePath toSelect = null;
2197 if (isLoadingNode(path.getLastPathComponent())) {
2198 final TreePath parentPath = path.getParentPath();
2199 if (parentPath != null) {
2200 if (isValidForSelectionAdjusting((TreeNode)parentPath.getLastPathComponent())) {
2201 toSelect = parentPath;
2212 if (toSelect != null) {
2213 myTree.addSelectionPath(toSelect);
2215 if (isAdjustedSelection && myUpdaterState != null) {
2216 final Object toSelectElement = getElementFor(toSelect.getLastPathComponent());
2217 myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement, adjustmentCause);
2224 private static TreePath getPathFor(TreeNode node) {
2225 if (node instanceof DefaultMutableTreeNode) {
2226 return new TreePath(((DefaultMutableTreeNode)node).getPath());
2229 ArrayList nodes = new ArrayList();
2230 TreeNode eachParent = node;
2231 while (eachParent != null) {
2232 nodes.add(eachParent);
2233 eachParent = eachParent.getParent();
2236 return new TreePath(ArrayUtil.toObjectArray(nodes));
2241 private void removeNodeFromParent(final MutableTreeNode node, final boolean willAdjustSelection) {
2242 processInnerChange(new Runnable() {
2244 if (willAdjustSelection) {
2245 final TreePath path = getPathFor(node);
2246 if (myTree.isPathSelected(path)) {
2247 myTree.removeSelectionPath(path);
2251 myTreeModel.removeNodeFromParent(node);
2256 private void expandPath(final TreePath path, final boolean canSmartExpand) {
2257 processInnerChange(new Runnable() {
2259 if (path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2260 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
2261 if (node.getChildCount() > 0 && !myTree.isExpanded(path)) {
2262 if (!canSmartExpand) {
2263 myNotForSmartExpand.add(node);
2266 myRequestedExpand = path;
2267 myTree.expandPath(path);
2268 processSmartExpand(node, canSmartExpand, false);
2271 myNotForSmartExpand.remove(node);
2272 myRequestedExpand = null;
2276 processNodeActionsIfReady(node);
2283 private void processInnerChange(Runnable runnable) {
2284 if (myUpdaterState == null) {
2285 setUpdaterState(new UpdaterTreeState(this));
2288 myUpdaterState.process(runnable);
2291 private boolean isInnerChange() {
2292 return myUpdaterState != null && myUpdaterState.isProcessingNow();
2295 protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
2296 return descriptor.update();
2299 private void makeLoadingOrLeafIfNoChildren(final DefaultMutableTreeNode node) {
2300 TreePath path = getPathFor(node);
2301 if (path == null) return;
2303 insertLoadingNode(node, true);
2305 final NodeDescriptor descriptor = getDescriptorFrom(node);
2306 if (descriptor == null) return;
2308 descriptor.setChildrenSortingStamp(-1);
2310 if (getBuilder().isAlwaysShowPlus(descriptor)) return;
2313 TreePath parentPath = path.getParentPath();
2314 if (myTree.isVisible(path) || (parentPath != null && myTree.isExpanded(parentPath))) {
2315 if (myTree.isExpanded(path)) {
2316 addSubtreeToUpdate(node);
2319 insertLoadingNode(node, false);
2325 private boolean isValid(DefaultMutableTreeNode node) {
2326 if (node == null) return false;
2327 final Object object = node.getUserObject();
2328 if (object instanceof NodeDescriptor) {
2329 return isValid((NodeDescriptor)object);
2335 private boolean isValid(NodeDescriptor descriptor) {
2336 if (descriptor == null) return false;
2337 return isValid(getElementFromDescriptor(descriptor));
2340 private boolean isValid(Object element) {
2341 if (element instanceof ValidateableNode) {
2342 if (!((ValidateableNode)element).isValid()) return false;
2344 return getBuilder().validateNode(element);
2347 private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) {
2348 if (!isLoadingChildrenFor(node)) {
2349 myTreeModel.insertNodeInto(new LoadingNode(), node, 0);
2358 protected void queueToBackground(@NotNull final Runnable bgBuildAction,
2359 @Nullable final Runnable edtPostRunnable,
2360 @Nullable final Runnable finalizeEdtRunnable) {
2361 registerWorkerTask(bgBuildAction);
2363 final Runnable pooledThreadWithProgressRunnable = new Runnable() {
2369 final AbstractTreeBuilder builder = getBuilder();
2371 builder.runBackgroundLoading(new Runnable() {
2373 assertNotDispatchThread();
2380 bgBuildAction.run();
2382 if (edtPostRunnable != null && !isReleased()) {
2383 builder.updateAfterLoadedInBackground(new Runnable() {
2386 assertIsDispatchThread();
2392 edtPostRunnable.run();
2395 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2401 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2404 catch (ProcessCanceledException e) {
2405 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2407 catch (Throwable t) {
2408 unregisterWorkerTask(bgBuildAction, finalizeEdtRunnable);
2409 throw new RuntimeException(t);
2416 Runnable pooledThreadRunnable = new Runnable() {
2418 if (isReleased()) return;
2421 if (myProgress != null) {
2422 ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress);
2425 pooledThreadWithProgressRunnable.run();
2428 catch (ProcessCanceledException e) {
2434 if (isPassthroughMode()) {
2437 if (myWorker == null || myWorker.isDisposed()) {
2438 myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1);
2440 myWorker.addTaskFirst(pooledThreadRunnable);
2441 myWorker.dispose(false);
2444 myWorker.addTaskFirst(pooledThreadRunnable);
2449 private void registerWorkerTask(Runnable runnable) {
2450 synchronized (myActiveWorkerTasks) {
2451 myActiveWorkerTasks.add(runnable);
2455 private void unregisterWorkerTask(Runnable runnable, @Nullable Runnable finalizeRunnable) {
2457 synchronized (myActiveWorkerTasks) {
2458 wasRemoved = myActiveWorkerTasks.remove(runnable);
2461 if (wasRemoved && finalizeRunnable != null) {
2462 finalizeRunnable.run();
2468 public boolean isWorkerBusy() {
2469 synchronized (myActiveWorkerTasks) {
2470 return myActiveWorkerTasks.size() > 0;
2474 private void clearWorkerTasks() {
2475 synchronized (myActiveWorkerTasks) {
2476 myActiveWorkerTasks.clear();
2480 private void updateNodeImageAndPosition(final DefaultMutableTreeNode node, boolean updatePosition) {
2481 if (!(node.getUserObject() instanceof NodeDescriptor)) return;
2482 NodeDescriptor descriptor = getDescriptorFrom(node);
2483 if (getElementFromDescriptor(descriptor) == null) return;
2485 boolean notified = false;
2486 if (updatePosition) {
2487 DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
2488 if (parentNode != null) {
2489 int oldIndex = parentNode.getIndex(node);
2490 int newIndex = oldIndex;
2491 if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) {
2492 final ArrayList<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount());
2493 for (int i = 0; i < parentNode.getChildCount(); i++) {
2494 children.add(parentNode.getChildAt(i));
2496 sortChildren(node, children, true, false);
2497 newIndex = children.indexOf(node);
2500 if (oldIndex != newIndex) {
2501 List<Object> pathsToExpand = new ArrayList<Object>();
2502 List<Object> selectionPaths = new ArrayList<Object>();
2503 TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false);
2504 removeNodeFromParent(node, false);
2505 myTreeModel.insertNodeInto(node, parentNode, newIndex);
2506 TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false);
2510 myTreeModel.nodeChanged(node);
2515 myTreeModel.nodeChanged(node);
2521 myTreeModel.nodeChanged(node);
2526 public DefaultTreeModel getTreeModel() {
2530 private void insertNodesInto(final ArrayList<TreeNode> toInsert, final DefaultMutableTreeNode parentNode) {
2531 sortChildren(parentNode, toInsert, false, true);
2532 final ArrayList<TreeNode> all = new ArrayList<TreeNode>(toInsert.size() + parentNode.getChildCount());
2533 all.addAll(toInsert);
2534 all.addAll(TreeUtil.childrenToArray(parentNode));
2536 if (toInsert.size() > 0) {
2537 sortChildren(parentNode, all, true, true);
2539 int[] newNodeIndices = new int[toInsert.size()];
2540 int eachNewNodeIndex = 0;
2541 TreeMap<Integer, TreeNode> insertSet = new TreeMap<Integer, TreeNode>();
2542 for (int i = 0; i < toInsert.size(); i++) {
2543 TreeNode eachNewNode = toInsert.get(i);
2544 while (all.get(eachNewNodeIndex) != eachNewNode) {
2547 newNodeIndices[i] = eachNewNodeIndex;
2548 insertSet.put(eachNewNodeIndex, eachNewNode);
2551 Iterator<Integer> indices = insertSet.keySet().iterator();
2552 while (indices.hasNext()) {
2553 Integer eachIndex = indices.next();
2554 TreeNode eachNode = insertSet.get(eachIndex);
2555 parentNode.insert((MutableTreeNode)eachNode, eachIndex);
2558 myTreeModel.nodesWereInserted(parentNode, newNodeIndices);
2561 ArrayList<TreeNode> before = new ArrayList<TreeNode>();
2564 sortChildren(parentNode, all, true, false);
2565 if (!before.equals(all)) {
2566 processInnerChange(new Runnable() {
2568 parentNode.removeAllChildren();
2569 for (TreeNode each : all) {
2570 parentNode.add((MutableTreeNode)each);
2572 myTreeModel.nodeStructureChanged(parentNode);
2579 private void sortChildren(DefaultMutableTreeNode node, ArrayList<TreeNode> children, boolean updateStamp, boolean forceSort) {
2580 NodeDescriptor descriptor = getDescriptorFrom(node);
2581 assert descriptor != null;
2583 if (descriptor.getChildrenSortingStamp() >= getComparatorStamp() && !forceSort) return;
2584 if (children.size() > 0) {
2585 getBuilder().sortChildren(myNodeComparator, node, children);
2589 descriptor.setChildrenSortingStamp(getComparatorStamp());
2593 private void disposeNode(DefaultMutableTreeNode node) {
2594 TreeNode parent = node.getParent();
2595 if (parent instanceof DefaultMutableTreeNode) {
2596 addToUnbuilt((DefaultMutableTreeNode)parent);
2599 if (node.getChildCount() > 0) {
2600 for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) {
2605 removeFromUpdating(node);
2606 removeFromUnbuilt(node);
2608 if (isLoadingNode(node)) return;
2609 NodeDescriptor descriptor = getDescriptorFrom(node);
2610 if (descriptor == null) return;
2611 final Object element = getElementFromDescriptor(descriptor);
2612 removeMapping(element, node, null);
2613 myAutoExpandRoots.remove(element);
2614 node.setUserObject(null);
2615 node.removeAllChildren();
2618 public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root) {
2619 return addSubtreeToUpdate(root, null);
2622 public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root, Runnable runAfterUpdate) {
2623 Object element = getElementFor(root);
2624 if (getTreeStructure().isAlwaysLeaf(element)) {
2625 removeLoading(root, true);
2627 if (runAfterUpdate != null) {
2628 getReady(this).doWhenDone(runAfterUpdate);
2633 getUpdater().runAfterUpdate(runAfterUpdate);
2634 getUpdater().addSubtreeToUpdate(root);
2639 public boolean wasRootNodeInitialized() {
2640 return myRootNodeWasInitialized;
2643 private boolean isRootNodeBuilt() {
2644 return myRootNodeWasInitialized && isNodeBeingBuilt(myRootNode);
2647 public void select(final Object[] elements, @Nullable final Runnable onDone) {
2648 select(elements, onDone, false);
2651 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
2652 select(elements, onDone, addToSelection, false);
2655 public void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) {
2656 _select(elements, onDone, addToSelection, true, false, true, deferred, false, false);
2659 void _select(final Object[] elements,
2660 final Runnable onDone,
2661 final boolean addToSelection,
2662 final boolean checkCurrentSelection,
2663 final boolean checkIfInStructure) {
2665 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, true, false, false, false);
2668 void _select(final Object[] elements,
2669 final Runnable onDone,
2670 final boolean addToSelection,
2671 final boolean checkCurrentSelection,
2672 final boolean checkIfInStructure,
2673 final boolean scrollToVisible) {
2675 _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, false, false, false);
2678 public void userSelect(final Object[] elements,
2679 final Runnable onDone,
2680 final boolean addToSelection,
2682 _select(elements, onDone, addToSelection, true, false, scroll, false, true, true);
2685 void _select(final Object[] elements,
2686 final Runnable onDone,
2687 final boolean addToSelection,
2688 final boolean checkCurrentSelection,
2689 final boolean checkIfInStructure,
2690 final boolean scrollToVisible,
2691 final boolean deferred,
2692 final boolean canSmartExpand,
2693 final boolean mayQueue) {
2695 AbstractTreeUpdater updater = getUpdater();
2696 if (mayQueue && updater != null) {
2697 updater.queueSelection(new SelectionRequest(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, deferred, canSmartExpand));
2701 boolean willAffectSelection = elements.length > 0 || (elements.length == 0 && addToSelection);
2702 if (!willAffectSelection) {
2707 final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections;
2709 if (!deferred && wasRootNodeInitialized() && willAffectSelection) {
2710 myCanProcessDeferredSelections = false;
2713 if (!checkDeferred(deferred, onDone)) return;
2715 if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) {
2716 getTree().clearSelection();
2720 runDone(new Runnable() {
2722 if (!checkDeferred(deferred, onDone)) return;
2724 final Set<Object> currentElements = getSelectedElements();
2726 if (checkCurrentSelection && currentElements.size() > 0 && elements.length == currentElements.size()) {
2727 boolean runSelection = false;
2728 for (Object eachToSelect : elements) {
2729 if (!currentElements.contains(eachToSelect)) {
2730 runSelection = true;
2735 if (!runSelection) {
2736 if (elements.length > 0) {
2737 selectVisible(elements[0], onDone, true, true, scrollToVisible);
2743 Set<Object> toSelect = new HashSet<Object>();
2744 myTree.clearSelection();
2745 toSelect.addAll(Arrays.asList(elements));
2746 if (addToSelection) {
2747 toSelect.addAll(currentElements);
2750 if (checkIfInStructure) {
2751 final Iterator<Object> allToSelect = toSelect.iterator();
2752 while (allToSelect.hasNext()) {
2753 Object each = allToSelect.next();
2754 if (!isInStructure(each)) {
2755 allToSelect.remove();
2760 final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect);
2762 if (wasRootNodeInitialized()) {
2763 final int[] originalRows = myTree.getSelectionRows();
2764 if (!addToSelection) {
2765 myTree.clearSelection();
2767 addNext(elementsToSelect, 0, new Runnable() {
2769 if (getTree().isSelectionEmpty()) {
2770 restoreSelection(currentElements);
2774 }, originalRows, deferred, scrollToVisible, canSmartExpand);
2777 addToDeferred(elementsToSelect, onDone);
2783 private void restoreSelection(Set<Object> selection) {
2784 for (Object each : selection) {
2785 DefaultMutableTreeNode node = getNodeForElement(each, false);
2786 if (node != null && isValidForSelectionAdjusting(node)) {
2787 addSelectionPath(getPathFor(node), false, null, null);
2793 private void addToDeferred(final Object[] elementsToSelect, final Runnable onDone) {
2794 myDeferredSelections.clear();
2795 myDeferredSelections.add(new Runnable() {
2797 select(elementsToSelect, onDone, false, true);
2802 private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) {
2803 if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) {
2813 final Set<Object> getSelectedElements() {
2814 final TreePath[] paths = myTree.getSelectionPaths();
2816 Set<Object> result = new HashSet<Object>();
2817 if (paths != null) {
2818 for (TreePath eachPath : paths) {
2819 if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
2820 final DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent();
2821 final Object eachElement = getElementFor(eachNode);
2822 if (eachElement != null) {
2823 result.add(eachElement);
2832 private void addNext(final Object[] elements,
2834 @Nullable final Runnable onDone,
2835 final int[] originalRows,
2836 final boolean deferred,
2837 final boolean scrollToVisible,
2838 final boolean canSmartExpand) {
2839 if (i >= elements.length) {
2840 if (myTree.isSelectionEmpty()) {
2841 myTree.setSelectionRows(originalRows);
2846 if (!checkDeferred(deferred, onDone)) {
2850 doSelect(elements[i], new Runnable() {
2852 if (!checkDeferred(deferred, onDone)) return;
2854 addNext(elements, i + 1, onDone, originalRows, deferred, scrollToVisible, canSmartExpand);
2856 }, true, deferred, i == 0, scrollToVisible, canSmartExpand);
2860 public void select(final Object element, @Nullable final Runnable onDone) {
2861 select(element, onDone, false);
2864 public void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {