| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.ide.util.treeView; |
| |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.ide.UiActivity; |
| import com.intellij.ide.UiActivityMonitor; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.*; |
| import com.intellij.openapi.project.IndexNotReadyException; |
| import com.intellij.openapi.util.*; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.util.registry.RegistryValue; |
| import com.intellij.ui.LoadingNode; |
| import com.intellij.ui.treeStructure.AlwaysExpandedTree; |
| import com.intellij.ui.treeStructure.Tree; |
| import com.intellij.util.Alarm; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.concurrency.WorkerThread; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.HashSet; |
| import com.intellij.util.enumeration.EnumerationCopy; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.tree.TreeUtil; |
| import com.intellij.util.ui.update.Activatable; |
| import com.intellij.util.ui.update.UiNotifyConnector; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.*; |
| import javax.swing.tree.*; |
| import java.awt.*; |
| import java.awt.event.FocusAdapter; |
| import java.awt.event.FocusEvent; |
| import java.util.*; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| public class AbstractTreeUi { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeBuilder"); |
| protected JTree myTree;// protected for TestNG |
| @SuppressWarnings({"WeakerAccess"}) |
| protected DefaultTreeModel myTreeModel; |
| private AbstractTreeStructure myTreeStructure; |
| private AbstractTreeUpdater myUpdater; |
| |
| private Comparator<NodeDescriptor> myNodeDescriptorComparator; |
| private final Comparator<TreeNode> myNodeComparator = new Comparator<TreeNode>() { |
| @Override |
| public int compare(TreeNode n1, TreeNode n2) { |
| if (isLoadingNode(n1) || isLoadingNode(n2)) return 0; |
| |
| NodeDescriptor nodeDescriptor1 = getDescriptorFrom(n1); |
| NodeDescriptor nodeDescriptor2 = getDescriptorFrom(n2); |
| |
| if (nodeDescriptor1 == null || nodeDescriptor2 == null) return 0; |
| |
| return myNodeDescriptorComparator != null |
| ? myNodeDescriptorComparator.compare(nodeDescriptor1, nodeDescriptor2) |
| : nodeDescriptor1.getIndex() - nodeDescriptor2.getIndex(); |
| } |
| }; |
| long myOwnComparatorStamp; |
| private long myLastComparatorStamp; |
| |
| private DefaultMutableTreeNode myRootNode; |
| private final Map<Object, Object> myElementToNodeMap = new HashMap<Object, Object>(); |
| private final Set<DefaultMutableTreeNode> myUnbuiltNodes = new HashSet<DefaultMutableTreeNode>(); |
| private TreeExpansionListener myExpansionListener; |
| private MySelectionListener mySelectionListener; |
| |
| private WorkerThread myWorker = null; |
| private final Set<Runnable> myActiveWorkerTasks = new HashSet<Runnable>(); |
| |
| private ProgressIndicator myProgress; |
| private AbstractTreeNode<Object> TREE_NODE_WRAPPER; |
| |
| private boolean myRootNodeWasQueuedToInitialize = false; |
| private boolean myRootNodeInitialized = false; |
| |
| private final Map<Object, List<NodeAction>> myNodeActions = new HashMap<Object, List<NodeAction>>(); |
| private boolean myUpdateFromRootRequested; |
| private boolean myWasEverShown; |
| private boolean myUpdateIfInactive; |
| |
| private final Map<Object, UpdateInfo> myLoadedInBackground = new HashMap<Object, UpdateInfo>(); |
| private final Map<Object, List<NodeAction>> myNodeChildrenActions = new HashMap<Object, List<NodeAction>>(); |
| |
| private long myClearOnHideDelay = -1; |
| private volatile long ourUi2Countdown; |
| |
| private final Set<Runnable> myDeferredSelections = new HashSet<Runnable>(); |
| private final Set<Runnable> myDeferredExpansions = new HashSet<Runnable>(); |
| |
| private boolean myCanProcessDeferredSelections; |
| |
| private UpdaterTreeState myUpdaterState; |
| private AbstractTreeBuilder myBuilder; |
| |
| private final Set<DefaultMutableTreeNode> myUpdatingChildren = new THashSet<DefaultMutableTreeNode>(); |
| |
| private boolean myCanYield = false; |
| |
| private final List<TreeUpdatePass> myYieldingPasses = new ArrayList<TreeUpdatePass>(); |
| |
| private boolean myYieldingNow; |
| |
| private final Set<DefaultMutableTreeNode> myPendingNodeActions = new HashSet<DefaultMutableTreeNode>(); |
| private final Set<Runnable> myYieldingDoneRunnables = new HashSet<Runnable>(); |
| |
| private final Alarm myBusyAlarm = new Alarm(); |
| private final Runnable myWaiterForReady = new Runnable() { |
| @Override |
| public void run() { |
| maybeSetBusyAndScheduleWaiterForReady(false, null); |
| } |
| }; |
| |
| private final RegistryValue myYieldingUpdate = Registry.get("ide.tree.yieldingUiUpdate"); |
| private final RegistryValue myShowBusyIndicator = Registry.get("ide.tree.showBusyIndicator"); |
| private final RegistryValue myWaitForReadyTime = Registry.get("ide.tree.waitForReadyTimeout"); |
| |
| private boolean myWasEverIndexNotReady; |
| private boolean myShowing; |
| private final FocusAdapter myFocusListener = new FocusAdapter() { |
| @Override |
| public void focusGained(FocusEvent e) { |
| maybeReady(); |
| } |
| }; |
| private final Set<DefaultMutableTreeNode> myNotForSmartExpand = new HashSet<DefaultMutableTreeNode>(); |
| private TreePath myRequestedExpand; |
| |
| private TreePath mySilentExpand; |
| private TreePath mySilentSelect; |
| |
| private final ActionCallback myInitialized = new ActionCallback(); |
| private final BusyObject.Impl myBusyObject = new BusyObject.Impl() { |
| @Override |
| public boolean isReady() { |
| return AbstractTreeUi.this.isReady(true); |
| } |
| |
| @Override |
| protected void onReadyWasSent() { |
| removeActivity(); |
| } |
| }; |
| |
| private boolean myPassThroughMode = false; |
| |
| private final Set<Object> myAutoExpandRoots = new HashSet<Object>(); |
| private final RegistryValue myAutoExpandDepth = Registry.get("ide.tree.autoExpandMaxDepth"); |
| |
| private final Set<DefaultMutableTreeNode> myWillBeExpanded = new HashSet<DefaultMutableTreeNode>(); |
| private SimpleTimerTask myCleanupTask; |
| |
| private final AtomicBoolean myCancelRequest = new AtomicBoolean(); |
| private final ReentrantLock myStateLock = new ReentrantLock(); |
| |
| private final AtomicBoolean myResettingToReadyNow = new AtomicBoolean(); |
| |
| private final Map<Progressive, ProgressIndicator> myBatchIndicators = new HashMap<Progressive, ProgressIndicator>(); |
| private final Map<Progressive, ActionCallback> myBatchCallbacks = new HashMap<Progressive, ActionCallback>(); |
| |
| private final Map<DefaultMutableTreeNode, DefaultMutableTreeNode> myCancelledBuild = new WeakHashMap<DefaultMutableTreeNode, DefaultMutableTreeNode>(); |
| |
| private boolean mySelectionIsAdjusted; |
| private boolean myReleaseRequested; |
| |
| private boolean mySelectionIsBeingAdjusted; |
| |
| private final Set<Object> myRevalidatedObjects = new HashSet<Object>(); |
| |
| private final Set<Runnable> myUserRunnables = new HashSet<Runnable>(); |
| |
| private UiActivityMonitor myActivityMonitor; |
| @NonNls private UiActivity myActivityId; |
| |
| protected void init(@NotNull AbstractTreeBuilder builder, |
| @NotNull JTree tree, |
| @NotNull DefaultTreeModel treeModel, |
| AbstractTreeStructure treeStructure, |
| @Nullable Comparator<NodeDescriptor> comparator, |
| boolean updateIfInactive) { |
| myBuilder = builder; |
| myTree = tree; |
| myTreeModel = treeModel; |
| myActivityMonitor = UiActivityMonitor.getInstance(); |
| myActivityId = new UiActivity.AsyncBgOperation("TreeUi" + this); |
| addModelListenerToDianoseAccessOutsideEdt(); |
| TREE_NODE_WRAPPER = builder.createSearchingTreeNodeWrapper(); |
| myTree.setModel(myTreeModel); |
| setRootNode((DefaultMutableTreeNode)treeModel.getRoot()); |
| myTreeStructure = treeStructure; |
| myNodeDescriptorComparator = comparator; |
| myUpdateIfInactive = updateIfInactive; |
| |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| if (!wasRootNodeInitialized()) { |
| if (myRootNode.getChildCount() == 0) { |
| insertLoadingNode(myRootNode, true); |
| } |
| } |
| } |
| }); |
| |
| myExpansionListener = new MyExpansionListener(); |
| myTree.addTreeExpansionListener(myExpansionListener); |
| |
| mySelectionListener = new MySelectionListener(); |
| myTree.addTreeSelectionListener(mySelectionListener); |
| |
| setUpdater(getBuilder().createUpdater()); |
| myProgress = getBuilder().createProgressIndicator(); |
| Disposer.register(getBuilder(), getUpdater()); |
| |
| final UiNotifyConnector uiNotify = new UiNotifyConnector(tree, new Activatable() { |
| @Override |
| public void showNotify() { |
| myShowing = true; |
| myWasEverShown = true; |
| if (canInitiateNewActivity()) { |
| activate(true); |
| } |
| } |
| |
| @Override |
| public void hideNotify() { |
| myShowing = false; |
| if (canInitiateNewActivity()) { |
| deactivate(); |
| } |
| } |
| }); |
| Disposer.register(getBuilder(), uiNotify); |
| |
| myTree.addFocusListener(myFocusListener); |
| } |
| |
| |
| private boolean isNodeActionsPending() { |
| return !myNodeActions.isEmpty() || !myNodeChildrenActions.isEmpty(); |
| } |
| |
| private void clearNodeActions() { |
| myNodeActions.clear(); |
| myNodeChildrenActions.clear(); |
| } |
| |
| private void maybeSetBusyAndScheduleWaiterForReady(boolean forcedBusy, @Nullable Object element) { |
| if (!myShowBusyIndicator.asBoolean()) return; |
| |
| boolean canUpdateBusyState = false; |
| |
| if (forcedBusy) { |
| if (canYield() || element != null && getTreeStructure().isToBuildChildrenInBackground(element)) { |
| canUpdateBusyState = true; |
| } |
| } else { |
| canUpdateBusyState = true; |
| } |
| |
| if (!canUpdateBusyState) return; |
| |
| if (myTree instanceof Tree) { |
| final Tree tree = (Tree)myTree; |
| final boolean isBusy = !isReady(true) || forcedBusy; |
| if (isBusy && tree.isShowing()) { |
| tree.setPaintBusy(true); |
| myBusyAlarm.cancelAllRequests(); |
| myBusyAlarm.addRequest(myWaiterForReady, myWaitForReadyTime.asInteger()); |
| } |
| else { |
| tree.setPaintBusy(false); |
| } |
| } |
| } |
| |
| private void setHoldSize(boolean holdSize) { |
| if (myTree instanceof Tree) { |
| final Tree tree = (Tree)myTree; |
| tree.setHoldSize(holdSize); |
| } |
| } |
| |
| private void cleanUpAll() { |
| final long now = System.currentTimeMillis(); |
| final long timeToCleanup = ourUi2Countdown; |
| if (timeToCleanup != 0 && now >= timeToCleanup) { |
| ourUi2Countdown = 0; |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| if (!canInitiateNewActivity()) return; |
| |
| myCleanupTask = null; |
| getBuilder().cleanUp(); |
| } |
| }; |
| if (isPassthroughMode()) { |
| runnable.run(); |
| } |
| else { |
| invokeLaterIfNeeded(false, runnable); |
| } |
| } |
| } |
| |
| protected void doCleanUp() { |
| Runnable cleanup = new Runnable() { |
| @Override |
| public void run() { |
| if (canInitiateNewActivity()) { |
| cleanUpNow(); |
| } |
| } |
| }; |
| |
| if (isPassthroughMode()) { |
| cleanup.run(); |
| } |
| else { |
| invokeLaterIfNeeded(false, cleanup); |
| } |
| } |
| |
| @NotNull |
| ActionCallback invokeLaterIfNeeded(boolean forceEdt, @NotNull final Runnable runnable) { |
| final ActionCallback result = new ActionCallback(); |
| |
| Runnable actual = new Runnable() { |
| @Override |
| public void run() { |
| if (isReleased()) { |
| result.setRejected(); |
| } |
| else { |
| runnable.run(); |
| result.setDone(); |
| } |
| } |
| }; |
| |
| if (forceEdt) { |
| UIUtil.invokeLaterIfNeeded(actual); |
| } |
| else { |
| if (isPassthroughMode() || !isEdt() && !isTreeShowing() && !myWasEverShown) { |
| actual.run(); |
| } |
| else { |
| UIUtil.invokeLaterIfNeeded(actual); |
| } |
| } |
| |
| return result; |
| } |
| |
| public void activate(boolean byShowing) { |
| cancelCurrentCleanupTask(); |
| |
| myCanProcessDeferredSelections = true; |
| ourUi2Countdown = 0; |
| |
| if (!myWasEverShown || myUpdateFromRootRequested || myUpdateIfInactive) { |
| getBuilder().updateFromRoot(); |
| } |
| |
| getUpdater().showNotify(); |
| |
| myWasEverShown |= byShowing; |
| } |
| |
| private void cancelCurrentCleanupTask() { |
| if (myCleanupTask != null) { |
| myCleanupTask.cancel(); |
| myCleanupTask = null; |
| } |
| } |
| |
| void deactivate() { |
| getUpdater().hideNotify(); |
| myBusyAlarm.cancelAllRequests(); |
| |
| if (!myWasEverShown) return; |
| |
| if (!isReady()) { |
| cancelUpdate(); |
| myUpdateFromRootRequested = true; |
| } |
| |
| if (getClearOnHideDelay() >= 0) { |
| ourUi2Countdown = System.currentTimeMillis() + getClearOnHideDelay(); |
| scheduleCleanUpAll(); |
| } |
| } |
| |
| private void scheduleCleanUpAll() { |
| cancelCurrentCleanupTask(); |
| |
| myCleanupTask = SimpleTimer.getInstance().setUp(new Runnable() { |
| @Override |
| public void run() { |
| cleanUpAll(); |
| } |
| }, getClearOnHideDelay()); |
| } |
| |
| public void requestRelease() { |
| myReleaseRequested = true; |
| cancelUpdate().doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| releaseNow(); |
| } |
| }); |
| } |
| |
| public ProgressIndicator getProgress() { |
| return myProgress; |
| } |
| |
| private void releaseNow() { |
| try { |
| acquireLock(); |
| |
| myTree.removeTreeExpansionListener(myExpansionListener); |
| myTree.removeTreeSelectionListener(mySelectionListener); |
| myTree.removeFocusListener(myFocusListener); |
| |
| disposeNode(getRootNode()); |
| myElementToNodeMap.clear(); |
| getUpdater().cancelAllRequests(); |
| if (myWorker != null) { |
| myWorker.dispose(true); |
| clearWorkerTasks(); |
| } |
| TREE_NODE_WRAPPER.setValue(null); |
| if (myProgress != null) { |
| myProgress.cancel(); |
| } |
| |
| cancelCurrentCleanupTask(); |
| |
| myTree = null; |
| setUpdater(null); |
| myWorker = null; |
| myTreeStructure = null; |
| myBuilder.releaseUi(); |
| myBuilder = null; |
| |
| clearNodeActions(); |
| |
| myDeferredSelections.clear(); |
| myDeferredExpansions.clear(); |
| myYieldingDoneRunnables.clear(); |
| } |
| finally { |
| releaseLock(); |
| } |
| } |
| |
| public boolean isReleased() { |
| return myBuilder == null; |
| } |
| |
| void doExpandNodeChildren(@NotNull final DefaultMutableTreeNode node) { |
| if (!myUnbuiltNodes.contains(node)) return; |
| if (isLoadedInBackground(getElementFor(node))) return; |
| |
| AbstractTreeStructure structure = getTreeStructure(); |
| structure.asyncCommit().doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| addSubtreeToUpdate(node); |
| getUpdater().performUpdate(); |
| } |
| }); |
| //if (structure.hasSomethingToCommit()) structure.commit(); |
| } |
| |
| public final AbstractTreeStructure getTreeStructure() { |
| return myTreeStructure; |
| } |
| |
| public final JTree getTree() { |
| return myTree; |
| } |
| |
| @Nullable |
| private static NodeDescriptor getDescriptorFrom(Object node) { |
| if (node instanceof DefaultMutableTreeNode) { |
| Object userObject = ((DefaultMutableTreeNode)node).getUserObject(); |
| if (userObject instanceof NodeDescriptor) { |
| return (NodeDescriptor)userObject; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| public final DefaultMutableTreeNode getNodeForElement(Object element, final boolean validateAgainstStructure) { |
| DefaultMutableTreeNode result = null; |
| if (validateAgainstStructure) { |
| int index = 0; |
| while (true) { |
| final DefaultMutableTreeNode node = findNode(element, index); |
| if (node == null) break; |
| |
| if (isNodeValidForElement(element, node)) { |
| result = node; |
| break; |
| } |
| |
| index++; |
| } |
| } |
| else { |
| result = getFirstNode(element); |
| } |
| |
| |
| if (result != null && !isNodeInStructure(result)) { |
| disposeNode(result); |
| result = null; |
| } |
| |
| return result; |
| } |
| |
| private boolean isNodeInStructure(DefaultMutableTreeNode node) { |
| return TreeUtil.isAncestor(getRootNode(), node) && getRootNode() == myTreeModel.getRoot(); |
| } |
| |
| private boolean isNodeValidForElement(@NotNull final Object element, @NotNull final DefaultMutableTreeNode node) { |
| return isSameHierarchy(element, node) || isValidChildOfParent(element, node); |
| } |
| |
| private boolean isValidChildOfParent(@NotNull final Object element, @NotNull final DefaultMutableTreeNode node) { |
| final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent(); |
| final Object parentElement = getElementFor(parent); |
| if (!isInStructure(parentElement)) return false; |
| |
| if (parent instanceof ElementNode) { |
| return ((ElementNode)parent).isValidChild(element); |
| } |
| for (int i = 0; i < parent.getChildCount(); i++) { |
| final TreeNode child = parent.getChildAt(i); |
| final Object eachElement = getElementFor(child); |
| if (element.equals(eachElement)) return true; |
| } |
| |
| return false; |
| } |
| |
| private boolean isSameHierarchy(@Nullable Object eachParent, @Nullable DefaultMutableTreeNode eachParentNode) { |
| boolean valid; |
| while (true) { |
| if (eachParent == null) { |
| valid = eachParentNode == null; |
| break; |
| } |
| |
| if (!eachParent.equals(getElementFor(eachParentNode))) { |
| valid = false; |
| break; |
| } |
| |
| eachParent = getTreeStructure().getParentElement(eachParent); |
| eachParentNode = (DefaultMutableTreeNode)eachParentNode.getParent(); |
| } |
| return valid; |
| } |
| |
| @Nullable |
| public final DefaultMutableTreeNode getNodeForPath(@NotNull Object[] path) { |
| DefaultMutableTreeNode node = null; |
| for (final Object pathElement : path) { |
| node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement); |
| if (node == null) { |
| break; |
| } |
| } |
| return node; |
| } |
| |
| public final void buildNodeForElement(Object element) { |
| getUpdater().performUpdate(); |
| DefaultMutableTreeNode node = getNodeForElement(element, false); |
| if (node == null) { |
| final List<Object> elements = new ArrayList<Object>(); |
| while (true) { |
| element = getTreeStructure().getParentElement(element); |
| if (element == null) { |
| break; |
| } |
| elements.add(0, element); |
| } |
| |
| for (final Object element1 : elements) { |
| node = getNodeForElement(element1, false); |
| if (node != null) { |
| expand(node, true); |
| } |
| } |
| } |
| } |
| |
| public final void buildNodeForPath(@NotNull Object[] path) { |
| getUpdater().performUpdate(); |
| DefaultMutableTreeNode node = null; |
| for (final Object pathElement : path) { |
| node = node == null ? getFirstNode(pathElement) : findNodeForChildElement(node, pathElement); |
| if (node != null && node != path[path.length - 1]) { |
| expand(node, true); |
| } |
| } |
| } |
| |
| public final void setNodeDescriptorComparator(Comparator<NodeDescriptor> nodeDescriptorComparator) { |
| myNodeDescriptorComparator = nodeDescriptorComparator; |
| myLastComparatorStamp = -1; |
| getBuilder().queueUpdateFrom(getTreeStructure().getRootElement(), true); |
| } |
| |
| @NotNull |
| protected AbstractTreeBuilder getBuilder() { |
| return myBuilder; |
| } |
| |
| protected final void initRootNode() { |
| if (myUpdateIfInactive) { |
| activate(false); |
| } |
| else { |
| myUpdateFromRootRequested = true; |
| } |
| } |
| |
| private boolean initRootNodeNowIfNeeded(@NotNull final TreeUpdatePass pass) { |
| boolean wasCleanedUp = false; |
| if (myRootNodeWasQueuedToInitialize) { |
| Object root = getTreeStructure().getRootElement(); |
| assert root != null : "Root element cannot be null"; |
| |
| Object currentRoot = getElementFor(myRootNode); |
| |
| if (Comparing.equal(root, currentRoot)) return false; |
| |
| Object rootAgain = getTreeStructure().getRootElement(); |
| if (root != rootAgain && !root.equals(rootAgain)) { |
| assert false : "getRootElement() if called twice must return either root1 == root2 or root1.equals(root2)"; |
| } |
| |
| cleanUpNow(); |
| wasCleanedUp = true; |
| } |
| |
| if (myRootNodeWasQueuedToInitialize) return true; |
| |
| myRootNodeWasQueuedToInitialize = true; |
| |
| final Object rootElement = getTreeStructure().getRootElement(); |
| addNodeAction(rootElement, new NodeAction() { |
| @Override |
| public void onReady(final DefaultMutableTreeNode node) { |
| processDeferredActions(); |
| } |
| }, false); |
| |
| |
| final Ref<NodeDescriptor> rootDescriptor = new Ref<NodeDescriptor>(null); |
| final boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(rootElement); |
| |
| Runnable build = new Runnable() { |
| @Override |
| public void run() { |
| rootDescriptor.set(getTreeStructure().createDescriptor(rootElement, null)); |
| getRootNode().setUserObject(rootDescriptor.get()); |
| update(rootDescriptor.get(), true); |
| pass.addToUpdated(rootDescriptor.get()); |
| } |
| }; |
| |
| |
| Runnable update = new Runnable() { |
| @Override |
| public void run() { |
| Object fromDescriptor = getElementFromDescriptor(rootDescriptor.get()); |
| if (fromDescriptor != null) { |
| createMapping(fromDescriptor, getRootNode()); |
| } |
| |
| |
| insertLoadingNode(getRootNode(), true); |
| |
| boolean willUpdate = false; |
| if (isAutoExpand(rootDescriptor.get())) { |
| willUpdate = myUnbuiltNodes.contains(getRootNode()); |
| expand(getRootNode(), true); |
| } |
| ActionCallback callback; |
| if (willUpdate) { |
| callback = new ActionCallback.Done(); |
| } |
| else { |
| callback = updateNodeChildren(getRootNode(), pass, null, false, false, false, true, true); |
| } |
| callback.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (getRootNode().getChildCount() == 0) { |
| myTreeModel.nodeChanged(getRootNode()); |
| } |
| } |
| }); |
| } |
| }; |
| |
| if (bgLoading) { |
| queueToBackground(build, update).doWhenProcessed(new Runnable() { |
| @Override |
| public void run() { |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| myRootNodeInitialized = true; |
| processNodeActionsIfReady(myRootNode); |
| } |
| }); |
| } |
| }); |
| } |
| else { |
| build.run(); |
| update.run(); |
| myRootNodeInitialized = true; |
| processNodeActionsIfReady(myRootNode); |
| } |
| |
| return wasCleanedUp; |
| } |
| |
| private boolean isAutoExpand(NodeDescriptor descriptor) { |
| return isAutoExpand(descriptor, true); |
| } |
| |
| private boolean isAutoExpand(@Nullable NodeDescriptor descriptor, boolean validate) { |
| if (descriptor == null || isAlwaysExpandedTree()) return false; |
| |
| boolean autoExpand = getBuilder().isAutoExpandNode(descriptor); |
| |
| Object element = getElementFromDescriptor(descriptor); |
| if (validate) { |
| autoExpand = validateAutoExpand(autoExpand, element); |
| } |
| |
| if (!autoExpand && !myTree.isRootVisible()) { |
| if (element != null && element.equals(getTreeStructure().getRootElement())) return true; |
| } |
| |
| return autoExpand; |
| } |
| |
| private boolean validateAutoExpand(boolean autoExpand, Object element) { |
| if (autoExpand) { |
| int distance = getDistanceToAutoExpandRoot(element); |
| if (distance < 0) { |
| myAutoExpandRoots.add(element); |
| } |
| else { |
| if (distance >= myAutoExpandDepth.asInteger() - 1) { |
| autoExpand = false; |
| } |
| } |
| |
| if (autoExpand) { |
| DefaultMutableTreeNode node = getNodeForElement(element, false); |
| autoExpand = isInVisibleAutoExpandChain(node); |
| } |
| } |
| return autoExpand; |
| } |
| |
| private boolean isInVisibleAutoExpandChain(DefaultMutableTreeNode child) { |
| TreeNode eachParent = child; |
| while (eachParent != null) { |
| |
| if (myRootNode == eachParent) return true; |
| |
| NodeDescriptor eachDescriptor = getDescriptorFrom(eachParent); |
| if (!isAutoExpand(eachDescriptor, false)) { |
| TreePath path = getPathFor(eachParent); |
| return myWillBeExpanded.contains(path.getLastPathComponent()) || myTree.isExpanded(path) && myTree.isVisible(path); |
| } |
| eachParent = eachParent.getParent(); |
| } |
| |
| return false; |
| } |
| |
| private int getDistanceToAutoExpandRoot(Object element) { |
| int distance = 0; |
| |
| Object eachParent = element; |
| while (eachParent != null) { |
| if (myAutoExpandRoots.contains(eachParent)) break; |
| eachParent = getTreeStructure().getParentElement(eachParent); |
| distance++; |
| } |
| |
| return eachParent != null ? distance : -1; |
| } |
| |
| private boolean isAutoExpand(@NotNull DefaultMutableTreeNode node) { |
| return isAutoExpand(getDescriptorFrom(node)); |
| } |
| |
| private boolean isAlwaysExpandedTree() { |
| return myTree instanceof AlwaysExpandedTree && ((AlwaysExpandedTree)myTree).isAlwaysExpanded(); |
| } |
| |
| @NotNull |
| private AsyncResult<Boolean> update(@NotNull final NodeDescriptor nodeDescriptor, boolean now) { |
| final AsyncResult<Boolean> result = new AsyncResult<Boolean>(); |
| |
| if (now || isPassthroughMode()) { |
| result.setDone(update(nodeDescriptor)); |
| } |
| else { |
| Object element = getElementFromDescriptor(nodeDescriptor); |
| boolean bgLoading = getTreeStructure().isToBuildChildrenInBackground(element); |
| |
| boolean edt = isEdt(); |
| if (bgLoading) { |
| if (edt) { |
| final AtomicBoolean changes = new AtomicBoolean(); |
| queueToBackground(new Runnable() { |
| @Override |
| public void run() { |
| changes.set(update(nodeDescriptor)); |
| } |
| }, new Runnable() { |
| @Override |
| public void run() { |
| result.setDone(changes.get()); |
| } |
| } |
| ); |
| } |
| else { |
| result.setDone(update(nodeDescriptor)); |
| } |
| } |
| else { |
| if (edt || !myWasEverShown) { |
| result.setDone(update(nodeDescriptor)); |
| } |
| else { |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| result.setDone(update(nodeDescriptor)); |
| } |
| }); |
| } |
| }); |
| } |
| } |
| } |
| |
| result.doWhenDone(new Consumer<Boolean>() { |
| @Override |
| public void consume(final Boolean changes) { |
| if (changes) { |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| Object element = nodeDescriptor.getElement(); |
| DefaultMutableTreeNode node = getNodeForElement(element, false); |
| if (node != null) { |
| TreePath path = getPathFor(node); |
| if (myTree.isVisible(path)) { |
| updateNodeImageAndPosition(node, false, changes); |
| } |
| } |
| } |
| }); |
| } |
| } |
| }); |
| |
| |
| return result; |
| } |
| |
| private boolean update(@NotNull final NodeDescriptor nodeDescriptor) { |
| try { |
| final AtomicBoolean update = new AtomicBoolean(); |
| try { |
| acquireLock(); |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| nodeDescriptor.setUpdateCount(nodeDescriptor.getUpdateCount() + 1); |
| update.set(getBuilder().updateNodeDescriptor(nodeDescriptor)); |
| } |
| }); |
| } |
| finally { |
| releaseLock(); |
| } |
| return update.get(); |
| } |
| catch (IndexNotReadyException e) { |
| warnOnIndexNotReady(); |
| return false; |
| } |
| } |
| |
| public void assertIsDispatchThread() { |
| if (isPassthroughMode()) return; |
| |
| if ((isTreeShowing() || myWasEverShown) && !isEdt()) { |
| LOG.error("Must be in event-dispatch thread"); |
| } |
| } |
| |
| private static boolean isEdt() { |
| return SwingUtilities.isEventDispatchThread(); |
| } |
| |
| private boolean isTreeShowing() { |
| return myShowing; |
| } |
| |
| private void assertNotDispatchThread() { |
| if (isPassthroughMode()) return; |
| |
| if (isEdt()) { |
| LOG.error("Must not be in event-dispatch thread"); |
| } |
| } |
| |
| private void processDeferredActions() { |
| processDeferredActions(myDeferredSelections); |
| processDeferredActions(myDeferredExpansions); |
| } |
| |
| private static void processDeferredActions(@NotNull Set<Runnable> actions) { |
| final Runnable[] runnables = actions.toArray(new Runnable[actions.size()]); |
| actions.clear(); |
| for (Runnable runnable : runnables) { |
| runnable.run(); |
| } |
| } |
| |
| //todo: to make real callback |
| @NotNull |
| public ActionCallback queueUpdate(Object element) { |
| return queueUpdate(element, true); |
| } |
| |
| @NotNull |
| public ActionCallback queueUpdate(Object element, boolean updateStructure) { |
| assertIsDispatchThread(); |
| |
| try { |
| AbstractTreeUpdater updater = getUpdater(); |
| if (updater == null) { |
| return new ActionCallback.Rejected(); |
| } |
| |
| final ActionCallback result = new ActionCallback(); |
| Object eachParent = element; |
| while(eachParent != null) { |
| DefaultMutableTreeNode node = getNodeForElement(element, false); |
| if (node != null) { |
| addSubtreeToUpdate(node, updateStructure); |
| break; |
| } |
| |
| eachParent = getTreeStructure().getParentElement(eachParent); |
| } |
| |
| if (eachParent == null) { |
| addSubtreeToUpdate(getRootNode(), updateStructure); |
| } |
| |
| updater.runAfterUpdate(new Runnable() { |
| @Override |
| public void run() { |
| result.setDone(); |
| } |
| }); |
| return result; |
| } |
| catch (ProcessCanceledException e) { |
| return new ActionCallback.Rejected(); |
| } |
| } |
| |
| public void doUpdateFromRoot() { |
| updateSubtree(getRootNode(), false); |
| } |
| |
| public final void updateSubtree(@NotNull DefaultMutableTreeNode node, boolean canSmartExpand) { |
| updateSubtree(new TreeUpdatePass(node), canSmartExpand); |
| } |
| |
| public final void updateSubtree(@NotNull TreeUpdatePass pass, boolean canSmartExpand) { |
| if (getUpdater() != null) { |
| getUpdater().addSubtreeToUpdate(pass); |
| } |
| else { |
| updateSubtreeNow(pass, canSmartExpand); |
| } |
| } |
| |
| final void updateSubtreeNow(@NotNull TreeUpdatePass pass, boolean canSmartExpand) { |
| maybeSetBusyAndScheduleWaiterForReady(true, getElementFor(pass.getNode())); |
| setHoldSize(true); |
| |
| boolean consumed = initRootNodeNowIfNeeded(pass); |
| if (consumed) return; |
| |
| final DefaultMutableTreeNode node = pass.getNode(); |
| |
| if (!(node.getUserObject() instanceof NodeDescriptor)) return; |
| |
| if (pass.isUpdateStructure()) { |
| setUpdaterState(new UpdaterTreeState(this)).beforeSubtreeUpdate(); |
| |
| boolean forceUpdate = true; |
| TreePath path = getPathFor(node); |
| boolean invisible = !myTree.isExpanded(path) && (path.getParentPath() == null || !myTree.isExpanded(path.getParentPath())); |
| |
| if (invisible && myUnbuiltNodes.contains(node)) { |
| forceUpdate = false; |
| } |
| |
| updateNodeChildren(node, pass, null, false, canSmartExpand, forceUpdate, false, pass.isUpdateChildren()); |
| } |
| else { |
| updateRow(0, pass); |
| } |
| } |
| |
| private void updateRow(final int row, @NotNull final TreeUpdatePass pass) { |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| if (row >= getTree().getRowCount()) return; |
| |
| TreePath path = getTree().getPathForRow(row); |
| if (path != null) { |
| final NodeDescriptor descriptor = getDescriptorFrom(path.getLastPathComponent()); |
| if (descriptor != null) { |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); |
| maybeYeild(new ActiveRunnable() { |
| @NotNull |
| @Override |
| public ActionCallback run() { |
| ActionCallback result = new ActionCallback(); |
| update(descriptor, false).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| updateRow(row + 1, pass); |
| } |
| }).notify(result); |
| return result; |
| } |
| }, pass, node); |
| } |
| } |
| } |
| }); |
| } |
| |
| private boolean isToBuildInBackground(NodeDescriptor descriptor) { |
| return getTreeStructure().isToBuildChildrenInBackground(getBuilder().getTreeStructureElement(descriptor)); |
| } |
| |
| @NotNull |
| private UpdaterTreeState setUpdaterState(@NotNull UpdaterTreeState state) { |
| if (myUpdaterState != null && myUpdaterState.equals(state)) return state; |
| |
| final UpdaterTreeState oldState = myUpdaterState; |
| if (oldState == null) { |
| myUpdaterState = state; |
| return state; |
| } |
| else { |
| oldState.addAll(state); |
| return oldState; |
| } |
| } |
| |
| protected void doUpdateNode(@NotNull final DefaultMutableTreeNode node) { |
| Object userObject = node.getUserObject(); |
| if (!(userObject instanceof NodeDescriptor)) return; |
| final NodeDescriptor descriptor = (NodeDescriptor)userObject; |
| final Object prevElement = getElementFromDescriptor(descriptor); |
| if (prevElement == null) return; |
| update(descriptor, false).doWhenDone(new Consumer<Boolean>() { |
| @Override |
| public void consume(Boolean changes) { |
| if (!isValid(descriptor)) { |
| if (isInStructure(prevElement)) { |
| getUpdater().addSubtreeToUpdateByElement(getTreeStructure().getParentElement(prevElement)); |
| return; |
| } |
| } |
| if (changes) { |
| updateNodeImageAndPosition(node, false, changes); |
| } |
| } |
| }); |
| } |
| |
| public Object getElementFromDescriptor(NodeDescriptor descriptor) { |
| return getBuilder().getTreeStructureElement(descriptor); |
| } |
| |
| @NotNull |
| private ActionCallback updateNodeChildren(@NotNull final DefaultMutableTreeNode node, |
| @NotNull final TreeUpdatePass pass, |
| @Nullable final LoadedChildren loadedChildren, |
| final boolean forcedNow, |
| final boolean toSmartExpand, |
| final boolean forceUpdate, |
| final boolean descriptorIsUpToDate, |
| final boolean updateChildren) { |
| AbstractTreeStructure treeStructure = getTreeStructure(); |
| ActionCallback result = treeStructure.asyncCommit(); |
| result.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| removeFromCancelled(node); |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| doUpdateChildren(node, pass, loadedChildren, forcedNow, toSmartExpand, forceUpdate, descriptorIsUpToDate, updateChildren); |
| } |
| }); |
| } |
| catch (ProcessCanceledException e) { |
| addToCancelled(node); |
| throw e; |
| } |
| } |
| }); |
| |
| return result; |
| } |
| |
| private void doUpdateChildren(@NotNull final DefaultMutableTreeNode node, |
| @NotNull final TreeUpdatePass pass, |
| @Nullable final LoadedChildren loadedChildren, |
| boolean forcedNow, |
| final boolean toSmartExpand, |
| boolean forceUpdate, |
| boolean descriptorIsUpToDate, |
| final boolean updateChildren) { |
| try { |
| |
| final NodeDescriptor descriptor = getDescriptorFrom(node); |
| if (descriptor == null) { |
| removeFromUnbuilt(node); |
| removeLoading(node, true); |
| return; |
| } |
| |
| boolean descriptorIsReady = descriptorIsUpToDate || pass.isUpdated(descriptor); |
| |
| final boolean wasExpanded = myTree.isExpanded(new TreePath(node.getPath())) || isAutoExpand(node); |
| final boolean wasLeaf = node.getChildCount() == 0; |
| |
| |
| boolean bgBuild = isToBuildInBackground(descriptor); |
| boolean notRequiredToUpdateChildren = !forcedNow && !wasExpanded; |
| |
| if (notRequiredToUpdateChildren && forceUpdate) { |
| boolean alwaysPlus = getBuilder().isAlwaysShowPlus(descriptor); |
| if (alwaysPlus && wasLeaf) { |
| notRequiredToUpdateChildren = false; |
| } |
| else { |
| notRequiredToUpdateChildren = alwaysPlus; |
| if (notRequiredToUpdateChildren && !myUnbuiltNodes.contains(node)) { |
| removeChildren(node); |
| } |
| } |
| } |
| |
| final AtomicReference<LoadedChildren> preloaded = new AtomicReference<LoadedChildren>(loadedChildren); |
| |
| if (notRequiredToUpdateChildren) { |
| if (myUnbuiltNodes.contains(node) && node.getChildCount() == 0) { |
| insertLoadingNode(node, true); |
| } |
| |
| if (!descriptorIsReady) { |
| update(descriptor, false); |
| } |
| |
| return; |
| } |
| |
| if (!forcedNow && !bgBuild && myUnbuiltNodes.contains(node)) { |
| if (!descriptorIsReady) { |
| update(descriptor, true); |
| descriptorIsReady = true; |
| } |
| |
| if (processAlwaysLeaf(node) || !updateChildren) { |
| return; |
| } |
| |
| Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, descriptor, pass, wasExpanded, null); |
| |
| if (unbuilt.getFirst()) { |
| return; |
| } |
| preloaded.set(unbuilt.getSecond()); |
| } |
| |
| |
| final boolean childForceUpdate = isChildNodeForceUpdate(node, forceUpdate, wasExpanded); |
| |
| if (!forcedNow && isToBuildInBackground(descriptor)) { |
| boolean alwaysLeaf = processAlwaysLeaf(node); |
| queueBackgroundUpdate( |
| new UpdateInfo(descriptor, pass, canSmartExpand(node, toSmartExpand), wasExpanded, childForceUpdate, descriptorIsReady, |
| !alwaysLeaf && updateChildren), node); |
| } |
| else { |
| if (!descriptorIsReady) { |
| update(descriptor, false).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (processAlwaysLeaf(node) || !updateChildren) return; |
| updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, childForceUpdate); |
| } |
| }); |
| } |
| else { |
| if (processAlwaysLeaf(node) || !updateChildren) return; |
| |
| updateNodeChildrenNow(node, pass, preloaded.get(), toSmartExpand, wasExpanded, childForceUpdate); |
| } |
| } |
| } |
| finally { |
| if (!isReleased()) { |
| processNodeActionsIfReady(node); |
| } |
| } |
| } |
| |
| private boolean processAlwaysLeaf(@NotNull DefaultMutableTreeNode node) { |
| Object element = getElementFor(node); |
| NodeDescriptor desc = getDescriptorFrom(node); |
| |
| if (desc == null) return false; |
| |
| if (getTreeStructure().isAlwaysLeaf(element)) { |
| removeFromUnbuilt(node); |
| removeLoading(node, true); |
| |
| if (node.getChildCount() > 0) { |
| final TreeNode[] children = new TreeNode[node.getChildCount()]; |
| for (int i = 0; i < node.getChildCount(); i++) { |
| children[i] = node.getChildAt(i); |
| } |
| |
| if (isSelectionInside(node)) { |
| addSelectionPath(getPathFor(node), true, Conditions.alwaysTrue(), null); |
| } |
| |
| processInnerChange(new Runnable() { |
| @Override |
| public void run() { |
| for (TreeNode each : children) { |
| removeNodeFromParent((MutableTreeNode)each, true); |
| disposeNode((DefaultMutableTreeNode)each); |
| } |
| } |
| }); |
| } |
| |
| removeFromUnbuilt(node); |
| desc.setWasDeclaredAlwaysLeaf(true); |
| processNodeActionsIfReady(node); |
| return true; |
| } |
| else { |
| boolean wasLeaf = desc.isWasDeclaredAlwaysLeaf(); |
| desc.setWasDeclaredAlwaysLeaf(false); |
| |
| if (wasLeaf) { |
| insertLoadingNode(node, true); |
| } |
| |
| return false; |
| } |
| } |
| |
| private boolean isChildNodeForceUpdate(@NotNull DefaultMutableTreeNode node, boolean parentForceUpdate, boolean parentExpanded) { |
| TreePath path = getPathFor(node); |
| return parentForceUpdate && (parentExpanded || myTree.isExpanded(path)); |
| } |
| |
| private void updateNodeChildrenNow(@NotNull final DefaultMutableTreeNode node, |
| @NotNull final TreeUpdatePass pass, |
| @Nullable final LoadedChildren preloadedChildren, |
| final boolean toSmartExpand, |
| final boolean wasExpanded, |
| final boolean forceUpdate) { |
| if (isUpdatingChildrenNow(node)) return; |
| |
| if (!canInitiateNewActivity()) { |
| throw new ProcessCanceledException(); |
| } |
| |
| final NodeDescriptor descriptor = getDescriptorFrom(node); |
| |
| final MutualMap<Object, Integer> elementToIndexMap = loadElementsFromStructure(descriptor, preloadedChildren); |
| final LoadedChildren loadedChildren = |
| preloadedChildren != null ? preloadedChildren : new LoadedChildren(elementToIndexMap.getKeys().toArray()); |
| |
| |
| addToUpdatingChildren(node); |
| pass.setCurrentNode(node); |
| |
| final boolean canSmartExpand = canSmartExpand(node, toSmartExpand); |
| |
| removeFromUnbuilt(node); |
| |
| processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren) |
| .doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed(node)) { |
| removeFromUpdatingChildren(node); |
| return; |
| } |
| |
| removeLoading(node, false); |
| |
| final boolean expanded = isExpanded(node, wasExpanded); |
| |
| if (expanded) { |
| myWillBeExpanded.add(node); |
| } |
| else { |
| myWillBeExpanded.remove(node); |
| } |
| |
| collectNodesToInsert(descriptor, elementToIndexMap, node, expanded, loadedChildren) |
| .doWhenDone(new Consumer<List<TreeNode>>() { |
| @Override |
| public void consume(@NotNull final List<TreeNode> nodesToInsert) { |
| insertNodesInto(nodesToInsert, node); |
| ActionCallback callback = updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded)); |
| callback.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| removeLoading(node, false); |
| removeFromUpdatingChildren(node); |
| |
| if (node.getChildCount() > 0) { |
| if (expanded) { |
| expand(node, canSmartExpand); |
| } |
| } |
| |
| if (!canInitiateNewActivity()) { |
| throw new ProcessCanceledException(); |
| } |
| |
| final Object element = getElementFor(node); |
| addNodeAction(element, new NodeAction() { |
| @Override |
| public void onReady(@NotNull final DefaultMutableTreeNode node) { |
| removeLoading(node, false); |
| } |
| }, false); |
| |
| processNodeActionsIfReady(node); |
| } |
| }); |
| } |
| }).doWhenProcessed(new Runnable() { |
| @Override |
| public void run() { |
| myWillBeExpanded.remove(node); |
| removeFromUpdatingChildren(node); |
| processNodeActionsIfReady(node); |
| } |
| }); |
| } |
| }).doWhenRejected(new Runnable() { |
| @Override |
| public void run() { |
| removeFromUpdatingChildren(node); |
| processNodeActionsIfReady(node); |
| } |
| }); |
| } |
| |
| private boolean isDisposed(@NotNull DefaultMutableTreeNode node) { |
| return !node.isNodeAncestor((DefaultMutableTreeNode)myTree.getModel().getRoot()); |
| } |
| |
| private void expandSilently(TreePath path) { |
| assertIsDispatchThread(); |
| |
| try { |
| mySilentExpand = path; |
| getTree().expandPath(path); |
| } |
| finally { |
| mySilentExpand = null; |
| } |
| } |
| |
| private void addSelectionSilently(TreePath path) { |
| assertIsDispatchThread(); |
| |
| try { |
| mySilentSelect = path; |
| getTree().getSelectionModel().addSelectionPath(path); |
| } |
| finally { |
| mySilentSelect = null; |
| } |
| } |
| |
| private void expand(@NotNull DefaultMutableTreeNode node, boolean canSmartExpand) { |
| expand(new TreePath(node.getPath()), canSmartExpand); |
| } |
| |
| private void expand(@NotNull final TreePath path, boolean canSmartExpand) { |
| final Object last = path.getLastPathComponent(); |
| boolean isLeaf = myTree.getModel().isLeaf(path.getLastPathComponent()); |
| final boolean isRoot = last == myTree.getModel().getRoot(); |
| final TreePath parent = path.getParentPath(); |
| if (isRoot && !myTree.isExpanded(path)) { |
| if (myTree.isRootVisible() || myUnbuiltNodes.contains(last)) { |
| insertLoadingNode((DefaultMutableTreeNode)last, false); |
| } |
| expandPath(path, canSmartExpand); |
| } |
| else if (myTree.isExpanded(path) || |
| isLeaf && parent != null && myTree.isExpanded(parent) && !myUnbuiltNodes.contains(last) && !isCancelled(last)) { |
| if (last instanceof DefaultMutableTreeNode) { |
| processNodeActionsIfReady((DefaultMutableTreeNode)last); |
| } |
| } |
| else { |
| if (isLeaf && (myUnbuiltNodes.contains(last) || isCancelled(last))) { |
| insertLoadingNode((DefaultMutableTreeNode)last, true); |
| expandPath(path, canSmartExpand); |
| } |
| else if (isLeaf && parent != null) { |
| final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent.getLastPathComponent(); |
| if (parentNode != null) { |
| addToUnbuilt(parentNode); |
| } |
| expandPath(parent, canSmartExpand); |
| } |
| else { |
| expandPath(path, canSmartExpand); |
| } |
| } |
| } |
| |
| private void addToUnbuilt(DefaultMutableTreeNode node) { |
| myUnbuiltNodes.add(node); |
| } |
| |
| private void removeFromUnbuilt(DefaultMutableTreeNode node) { |
| myUnbuiltNodes.remove(node); |
| } |
| |
| private Pair<Boolean, LoadedChildren> processUnbuilt(@NotNull final DefaultMutableTreeNode node, |
| final NodeDescriptor descriptor, |
| @NotNull final TreeUpdatePass pass, |
| final boolean isExpanded, |
| @Nullable final LoadedChildren loadedChildren) { |
| final Ref<Pair<Boolean, LoadedChildren>> result = new Ref<Pair<Boolean, LoadedChildren>>(); |
| |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| if (!isExpanded && getBuilder().isAlwaysShowPlus(descriptor)) { |
| result.set(new Pair<Boolean, LoadedChildren>(true, null)); |
| return; |
| } |
| |
| final Object element = getElementFor(node); |
| |
| addToUpdatingChildren(node); |
| |
| try { |
| final LoadedChildren children = loadedChildren != null ? loadedChildren : new LoadedChildren(getChildrenFor(element)); |
| |
| boolean processed; |
| |
| if (children.getElements().isEmpty()) { |
| removeFromUnbuilt(node); |
| removeLoading(node, true); |
| processed = true; |
| } |
| else { |
| if (isAutoExpand(node)) { |
| addNodeAction(getElementFor(node), new NodeAction() { |
| @Override |
| public void onReady(@NotNull final DefaultMutableTreeNode node) { |
| final TreePath path = new TreePath(node.getPath()); |
| if (getTree().isExpanded(path) || children.getElements().isEmpty()) { |
| removeLoading(node, false); |
| } |
| else { |
| maybeYeild(new ActiveRunnable() { |
| @NotNull |
| @Override |
| public ActionCallback run() { |
| expand(element, null); |
| return new ActionCallback.Done(); |
| } |
| }, pass, node); |
| } |
| } |
| }, false); |
| } |
| processed = false; |
| } |
| |
| removeFromUpdatingChildren(node); |
| |
| processNodeActionsIfReady(node); |
| |
| result.set(new Pair<Boolean, LoadedChildren>(processed, children)); |
| } |
| finally { |
| removeFromUpdatingChildren(node); |
| } |
| } |
| }); |
| |
| return result.get(); |
| } |
| |
| private boolean removeIfLoading(@NotNull TreeNode node) { |
| if (isLoadingNode(node)) { |
| moveSelectionToParentIfNeeded(node); |
| removeNodeFromParent((MutableTreeNode)node, false); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private void moveSelectionToParentIfNeeded(@NotNull TreeNode node) { |
| TreePath path = getPathFor(node); |
| if (myTree.getSelectionModel().isPathSelected(path)) { |
| TreePath parentPath = path.getParentPath(); |
| myTree.getSelectionModel().removeSelectionPath(path); |
| if (parentPath != null) { |
| myTree.getSelectionModel().addSelectionPath(parentPath); |
| } |
| } |
| } |
| |
| //todo [kirillk] temporary consistency check |
| private Object[] getChildrenFor(final Object element) { |
| final Ref<Object[]> passOne = new Ref<Object[]>(); |
| try { |
| acquireLock(); |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| passOne.set(getTreeStructure().getChildElements(element)); |
| } |
| }); |
| } |
| catch (IndexNotReadyException e) { |
| warnOnIndexNotReady(); |
| return ArrayUtil.EMPTY_OBJECT_ARRAY; |
| } |
| finally { |
| releaseLock(); |
| } |
| |
| if (!Registry.is("ide.tree.checkStructure")) return passOne.get(); |
| |
| final Object[] passTwo = getTreeStructure().getChildElements(element); |
| |
| final HashSet<Object> two = new HashSet<Object>(Arrays.asList(passTwo)); |
| |
| if (passOne.get().length != passTwo.length) { |
| LOG.error( |
| "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" + |
| element); |
| } |
| else { |
| for (Object eachInOne : passOne.get()) { |
| if (!two.contains(eachInOne)) { |
| LOG.error( |
| "AbstractTreeStructure.getChildren() must either provide same objects or new objects but with correct hashCode() and equals() methods. Wrong parent element=" + |
| element); |
| break; |
| } |
| } |
| } |
| |
| return passOne.get(); |
| } |
| |
| private void warnOnIndexNotReady() { |
| if (!myWasEverIndexNotReady) { |
| myWasEverIndexNotReady = true; |
| LOG.warn("Tree is not dumb-mode-aware; treeBuilder=" + getBuilder() + " treeStructure=" + getTreeStructure()); |
| } |
| } |
| |
| @NotNull |
| private ActionCallback updateNodesToInsert(@NotNull final List<TreeNode> nodesToInsert, |
| @NotNull TreeUpdatePass pass, |
| boolean canSmartExpand, |
| boolean forceUpdate) { |
| ActionCallback.Chunk chunk = new ActionCallback.Chunk(); |
| for (TreeNode node : nodesToInsert) { |
| DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node; |
| ActionCallback callback = updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true, true); |
| if (!callback.isDone()) { |
| chunk.add(callback); |
| } |
| } |
| return chunk.getWhenProcessed(); |
| } |
| |
| @NotNull |
| private ActionCallback processExistingNodes(@NotNull final DefaultMutableTreeNode node, |
| @NotNull final MutualMap<Object, Integer> elementToIndexMap, |
| @NotNull final TreeUpdatePass pass, |
| final boolean canSmartExpand, |
| final boolean forceUpdate, |
| final boolean wasExpaned, |
| @Nullable final LoadedChildren preloaded) { |
| |
| final List<TreeNode> childNodes = TreeUtil.childrenToArray(node); |
| return maybeYeild(new ActiveRunnable() { |
| @NotNull |
| @Override |
| public ActionCallback run() { |
| if (pass.isExpired()) return new ActionCallback.Rejected(); |
| if (childNodes.isEmpty()) return new ActionCallback.Done(); |
| |
| |
| final ActionCallback result = new ActionCallback(childNodes.size()); |
| |
| for (TreeNode each : childNodes) { |
| final DefaultMutableTreeNode eachChild = (DefaultMutableTreeNode)each; |
| if (isLoadingNode(eachChild)) { |
| result.setDone(); |
| continue; |
| } |
| |
| final boolean childForceUpdate = isChildNodeForceUpdate(eachChild, forceUpdate, wasExpaned); |
| |
| maybeYeild(new ActiveRunnable() { |
| @NotNull |
| @Override |
| public ActionCallback run() { |
| NodeDescriptor descriptor = preloaded != null ? preloaded.getDescriptor(getElementFor(eachChild)) : null; |
| NodeDescriptor descriptorFromNode = getDescriptorFrom(eachChild); |
| if (descriptor != null) { |
| eachChild.setUserObject(descriptor); |
| if (descriptorFromNode != null) { |
| descriptor.setChildrenSortingStamp(descriptorFromNode.getChildrenSortingStamp()); |
| } |
| } else { |
| descriptor = descriptorFromNode; |
| } |
| |
| return processExistingNode(eachChild, descriptor, node, elementToIndexMap, pass, canSmartExpand, |
| childForceUpdate, preloaded); |
| } |
| }, pass, node).notify(result); |
| |
| if (result.isRejected()) { |
| break; |
| } |
| } |
| |
| return result; |
| } |
| }, pass, node); |
| } |
| |
| private boolean isRerunNeeded(@NotNull TreeUpdatePass pass) { |
| if (pass.isExpired() || !canInitiateNewActivity()) return false; |
| |
| final boolean rerunBecauseTreeIsHidden = !pass.isExpired() && !isTreeShowing() && getUpdater().isInPostponeMode(); |
| |
| return rerunBecauseTreeIsHidden || getUpdater().isRerunNeededFor(pass); |
| } |
| |
| @NotNull |
| private ActionCallback maybeYeild(@NotNull final ActiveRunnable processRunnable, @NotNull final TreeUpdatePass pass, final DefaultMutableTreeNode node) { |
| final ActionCallback result = new ActionCallback(); |
| |
| if (isRerunNeeded(pass)) { |
| getUpdater().requeue(pass); |
| result.setRejected(); |
| } |
| else { |
| if (isToYieldUpdateFor(node)) { |
| pass.setCurrentNode(node); |
| boolean wasRun = yieldAndRun(new Runnable() { |
| @Override |
| public void run() { |
| if (pass.isExpired()) { |
| result.setRejected(); |
| return; |
| } |
| |
| if (isRerunNeeded(pass)) { |
| runDone(new Runnable() { |
| @Override |
| public void run() { |
| if (!pass.isExpired()) { |
| getUpdater().requeue(pass); |
| } |
| } |
| }); |
| result.setRejected(); |
| } |
| else { |
| try { |
| execute(processRunnable).notify(result); |
| } |
| catch (ProcessCanceledException e) { |
| pass.expire(); |
| result.setRejected(); |
| cancelUpdate(); |
| } |
| } |
| } |
| }, pass); |
| if (!wasRun) { |
| result.setRejected(); |
| } |
| } |
| else { |
| try { |
| execute(processRunnable).notify(result); |
| } |
| catch (ProcessCanceledException e) { |
| pass.expire(); |
| result.setRejected(); |
| cancelUpdate(); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| @NotNull |
| private ActionCallback execute(@NotNull final ActiveRunnable runnable) throws ProcessCanceledException { |
| final ActionCallback result = new ActionCallback(); |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| runnable.run().notify(result); |
| } |
| }); |
| return result; |
| } |
| |
| private void execute(@NotNull Runnable runnable) throws ProcessCanceledException { |
| try { |
| if (!canInitiateNewActivity()) { |
| throw new ProcessCanceledException(); |
| } |
| |
| runnable.run(); |
| |
| if (!canInitiateNewActivity()) { |
| throw new ProcessCanceledException(); |
| } |
| } |
| catch (ProcessCanceledException e) { |
| if (!isReleased()) { |
| setCancelRequested(true); |
| resetToReady(); |
| } |
| throw e; |
| } |
| } |
| |
| private boolean canInitiateNewActivity() { |
| return !isCancelProcessed() && !myReleaseRequested && !isReleased(); |
| } |
| |
| @NotNull |
| private ActionCallback resetToReady() { |
| final ActionCallback result = new ActionCallback(); |
| |
| if (isReady()) { |
| result.setDone(); |
| return result; |
| } |
| |
| if (myResettingToReadyNow.get()) { |
| _getReady().notify(result); |
| return result; |
| } |
| |
| myResettingToReadyNow.set(true); |
| |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| if (!myResettingToReadyNow.get()) { |
| result.setDone(); |
| return; |
| } |
| |
| Progressive[] progressives = myBatchIndicators.keySet().toArray(new Progressive[myBatchIndicators.size()]); |
| for (Progressive each : progressives) { |
| myBatchIndicators.remove(each).cancel(); |
| myBatchCallbacks.remove(each).setRejected(); |
| } |
| |
| resetToReadyNow().notify(result); |
| } |
| }); |
| |
| return result; |
| } |
| |
| @NotNull |
| private ActionCallback resetToReadyNow() { |
| if (isReleased()) return new ActionCallback.Rejected(); |
| |
| assertIsDispatchThread(); |
| |
| DefaultMutableTreeNode[] uc; |
| synchronized (myUpdatingChildren) { |
| uc = myUpdatingChildren.toArray(new DefaultMutableTreeNode[myUpdatingChildren.size()]); |
| } |
| for (DefaultMutableTreeNode each : uc) { |
| resetIncompleteNode(each); |
| } |
| |
| |
| Object[] bg = myLoadedInBackground.keySet().toArray(new Object[myLoadedInBackground.size()]); |
| for (Object each : bg) { |
| final DefaultMutableTreeNode node = getNodeForElement(each, false); |
| if (node != null) { |
| resetIncompleteNode(node); |
| } |
| } |
| |
| myUpdaterState = null; |
| getUpdater().reset(); |
| |
| myYieldingNow = false; |
| myYieldingPasses.clear(); |
| myYieldingDoneRunnables.clear(); |
| |
| myNodeActions.clear(); |
| myNodeChildrenActions.clear(); |
| |
| synchronized (myUpdatingChildren) { |
| myUpdatingChildren.clear(); |
| } |
| myLoadedInBackground.clear(); |
| |
| myDeferredExpansions.clear(); |
| myDeferredSelections.clear(); |
| |
| ActionCallback result = _getReady(); |
| result.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| myResettingToReadyNow.set(false); |
| setCancelRequested(false); |
| } |
| }); |
| |
| maybeReady(); |
| |
| return result; |
| } |
| |
| public void addToCancelled(@NotNull DefaultMutableTreeNode node) { |
| myCancelledBuild.put(node, node); |
| } |
| |
| public void removeFromCancelled(@NotNull DefaultMutableTreeNode node) { |
| myCancelledBuild.remove(node); |
| } |
| |
| public boolean isCancelled(Object node) { |
| return node instanceof DefaultMutableTreeNode && myCancelledBuild.containsKey(node); |
| } |
| |
| private void resetIncompleteNode(@NotNull DefaultMutableTreeNode node) { |
| if (myReleaseRequested) return; |
| |
| addToCancelled(node); |
| |
| if (!isExpanded(node, false)) { |
| node.removeAllChildren(); |
| if (!getTreeStructure().isAlwaysLeaf(getElementFor(node))) { |
| insertLoadingNode(node, true); |
| } |
| } |
| else { |
| removeFromUnbuilt(node); |
| removeLoading(node, true); |
| } |
| } |
| |
| private boolean yieldAndRun(@NotNull final Runnable runnable, @NotNull final TreeUpdatePass pass) { |
| myYieldingPasses.add(pass); |
| myYieldingNow = true; |
| yield(new Runnable() { |
| @Override |
| public void run() { |
| if (isReleased()) return; |
| |
| runOnYieldingDone(new Runnable() { |
| @Override |
| public void run() { |
| if (isReleased()) return; |
| |
| executeYieldingRequest(runnable, pass); |
| } |
| }); |
| } |
| }); |
| |
| return true; |
| } |
| |
| public boolean isYeildingNow() { |
| return myYieldingNow; |
| } |
| |
| private boolean hasScheduledUpdates() { |
| return getUpdater().hasNodesToUpdate(); |
| } |
| |
| public boolean isReady() { |
| return isReady(false); |
| } |
| |
| public boolean isCancelledReady() { |
| return isReady(false) && !myCancelledBuild.isEmpty(); |
| } |
| |
| public boolean isReady(boolean attempt) { |
| if (attempt && myStateLock.isLocked()) return false; |
| |
| Boolean ready = checkValue(new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| return Boolean.valueOf(isIdle() && !hasPendingWork() && !isNodeActionsPending()); |
| } |
| }, attempt); |
| |
| return ready != null && ready.booleanValue(); |
| } |
| |
| @Nullable |
| private Boolean checkValue(@NotNull Computable<Boolean> computable, boolean attempt) { |
| boolean toRelease = true; |
| try { |
| if (attempt) { |
| if (!attemptLock()) { |
| toRelease = false; |
| return computable.compute(); |
| } |
| } |
| else { |
| acquireLock(); |
| } |
| return computable.compute(); |
| } |
| catch (InterruptedException e) { |
| LOG.info(e); |
| return null; |
| } |
| finally { |
| if (toRelease) { |
| releaseLock(); |
| } |
| } |
| } |
| |
| @NotNull |
| @NonNls |
| public String getStatus() { |
| return "isReady=" + isReady() + "\n" + |
| " isIdle=" + isIdle() + "\n" + |
| " isYeildingNow=" + isYeildingNow() + "\n" + |
| " isWorkerBusy=" + isWorkerBusy() + "\n" + |
| " hasUpdatingChildrenNow=" + hasUpdatingChildrenNow() + "\n" + |
| " isLoadingInBackgroundNow=" + isLoadingInBackgroundNow() + "\n" + |
| " hasPendingWork=" + hasPendingWork() + "\n" + |
| " hasNodesToUpdate=" + hasNodesToUpdate() + "\n" + |
| " updaterState=" + myUpdaterState + "\n" + |
| " hasScheduledUpdates=" + hasScheduledUpdates() + "\n" + |
| " isPostponedMode=" + getUpdater().isInPostponeMode() + "\n" + |
| " nodeActions=" + myNodeActions.keySet() + "\n" + |
| " nodeChildrenActions=" + myNodeChildrenActions.keySet() + "\n" + |
| "isReleased=" + isReleased() + "\n" + |
| " isReleaseRequested=" + isReleaseRequested() + "\n" + |
| "isCancelProcessed=" + isCancelProcessed() + "\n" + |
| " isCancelRequested=" + myCancelRequest + "\n" + |
| " isResettingToReadyNow=" + myResettingToReadyNow + "\n" + |
| "canInitiateNewActivity=" + canInitiateNewActivity() + |
| "batchIndicators=" + myBatchIndicators; |
| } |
| |
| public boolean hasPendingWork() { |
| return hasNodesToUpdate() || |
| myUpdaterState != null && myUpdaterState.isProcessingNow() || |
| hasScheduledUpdates() && !getUpdater().isInPostponeMode(); |
| } |
| |
| public boolean isIdle() { |
| return !isYeildingNow() && !isWorkerBusy() && !hasUpdatingChildrenNow() && !isLoadingInBackgroundNow(); |
| } |
| |
| private void executeYieldingRequest(@NotNull Runnable runnable, @NotNull TreeUpdatePass pass) { |
| try { |
| try { |
| myYieldingPasses.remove(pass); |
| |
| if (!canInitiateNewActivity()) { |
| throw new ProcessCanceledException(); |
| } |
| |
| runnable.run(); |
| } |
| finally { |
| if (!isReleased()) { |
| maybeYeildingFinished(); |
| } |
| } |
| } |
| catch (ProcessCanceledException e) { |
| resetToReady(); |
| } |
| } |
| |
| private void maybeYeildingFinished() { |
| if (myYieldingPasses.isEmpty()) { |
| myYieldingNow = false; |
| flushPendingNodeActions(); |
| } |
| } |
| |
| void maybeReady() { |
| assertIsDispatchThread(); |
| |
| if (isReleased()) return; |
| |
| boolean ready = isReady(true); |
| if (!ready) return; |
| myRevalidatedObjects.clear(); |
| |
| setCancelRequested(false); |
| myResettingToReadyNow.set(false); |
| |
| myInitialized.setDone(); |
| |
| if (canInitiateNewActivity()) { |
| if (myUpdaterState != null && !myUpdaterState.isProcessingNow()) { |
| UpdaterTreeState oldState = myUpdaterState; |
| if (!myUpdaterState.restore(null)) { |
| setUpdaterState(oldState); |
| } |
| |
| if (!isReady()) return; |
| } |
| } |
| |
| setHoldSize(false); |
| |
| if (myTree.isShowing()) { |
| if (getBuilder().isToEnsureSelectionOnFocusGained() && Registry.is("ide.tree.ensureSelectionOnFocusGained")) { |
| TreeUtil.ensureSelection(myTree); |
| } |
| } |
| |
| if (myInitialized.isDone()) { |
| if (isReleaseRequested() || isCancelProcessed()) { |
| myBusyObject.onReady(this); |
| } else { |
| myBusyObject.onReady(); |
| } |
| } |
| |
| if (canInitiateNewActivity()) { |
| TreePath[] selection = getTree().getSelectionPaths(); |
| Rectangle visible = getTree().getVisibleRect(); |
| if (selection != null) { |
| for (TreePath each : selection) { |
| Rectangle bounds = getTree().getPathBounds(each); |
| if (bounds != null && (visible.contains(bounds) || visible.intersects(bounds))) { |
| getTree().repaint(bounds); |
| } |
| } |
| } |
| } |
| } |
| |
| private void flushPendingNodeActions() { |
| final DefaultMutableTreeNode[] nodes = myPendingNodeActions.toArray(new DefaultMutableTreeNode[myPendingNodeActions.size()]); |
| myPendingNodeActions.clear(); |
| |
| for (DefaultMutableTreeNode each : nodes) { |
| processNodeActionsIfReady(each); |
| } |
| |
| final Runnable[] actions = myYieldingDoneRunnables.toArray(new Runnable[myYieldingDoneRunnables.size()]); |
| for (Runnable each : actions) { |
| if (!isYeildingNow()) { |
| myYieldingDoneRunnables.remove(each); |
| each.run(); |
| } |
| } |
| |
| maybeReady(); |
| } |
| |
| protected void runOnYieldingDone(Runnable onDone) { |
| getBuilder().runOnYeildingDone(onDone); |
| } |
| |
| protected void yield(Runnable runnable) { |
| getBuilder().yield(runnable); |
| } |
| |
| private boolean isToYieldUpdateFor(final DefaultMutableTreeNode node) { |
| return canYield() && getBuilder().isToYieldUpdateFor(node); |
| } |
| |
| @NotNull |
| private MutualMap<Object, Integer> loadElementsFromStructure(final NodeDescriptor descriptor, |
| @Nullable LoadedChildren preloadedChildren) { |
| MutualMap<Object, Integer> elementToIndexMap = new MutualMap<Object, Integer>(true); |
| final Object element = getBuilder().getTreeStructureElement(descriptor); |
| if (!isValid(element)) return elementToIndexMap; |
| |
| |
| List<Object> children = preloadedChildren != null |
| ? preloadedChildren.getElements() |
| : Arrays.asList(getChildrenFor(element)); |
| int index = 0; |
| for (Object child : children) { |
| if (!isValid(child)) continue; |
| elementToIndexMap.put(child, Integer.valueOf(index)); |
| index++; |
| } |
| return elementToIndexMap; |
| } |
| |
| public static boolean isLoadingNode(final Object node) { |
| return node instanceof LoadingNode; |
| } |
| |
| @NotNull |
| private AsyncResult<List<TreeNode>> collectNodesToInsert(final NodeDescriptor descriptor, |
| @NotNull final MutualMap<Object, Integer> elementToIndexMap, |
| final DefaultMutableTreeNode parent, |
| final boolean addLoadingNode, |
| @NotNull final LoadedChildren loadedChildren) { |
| final AsyncResult<List<TreeNode>> result = new AsyncResult<List<TreeNode>>(); |
| |
| final List<TreeNode> nodesToInsert = new ArrayList<TreeNode>(); |
| Collection<Object> allElements = elementToIndexMap.getKeys(); |
| |
| final ActionCallback processingDone = new ActionCallback(allElements.size()); |
| |
| for (final Object child : allElements) { |
| Integer index = elementToIndexMap.getValue(child); |
| boolean needToUpdate = false; |
| NodeDescriptor loadedDesc = loadedChildren.getDescriptor(child); |
| final NodeDescriptor childDescr; |
| if (loadedDesc == null) { |
| childDescr = getTreeStructure().createDescriptor(child, descriptor); |
| needToUpdate = true; |
| } |
| else { |
| childDescr = loadedDesc; |
| } |
| |
| if (index == null) { |
| index = Integer.MAX_VALUE; |
| needToUpdate = true; |
| } |
| |
| childDescr.setIndex(index.intValue()); |
| |
| final ActionCallback update = new ActionCallback(); |
| if (needToUpdate) { |
| update(childDescr, false).doWhenDone(new Consumer<Boolean>() { |
| @Override |
| public void consume(Boolean changes) { |
| loadedChildren.putDescriptor(child, childDescr, changes); |
| update.setDone(); |
| } |
| }); |
| } |
| else { |
| update.setDone(); |
| } |
| |
| update.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| Object element = getElementFromDescriptor(childDescr); |
| if (element != null) { |
| DefaultMutableTreeNode node = getNodeForElement(element, false); |
| if (node == null || node.getParent() != parent) { |
| final DefaultMutableTreeNode childNode = createChildNode(childDescr); |
| if (addLoadingNode || getBuilder().isAlwaysShowPlus(childDescr)) { |
| insertLoadingNode(childNode, true); |
| } |
| else { |
| addToUnbuilt(childNode); |
| } |
| nodesToInsert.add(childNode); |
| createMapping(element, childNode); |
| } |
| } |
| processingDone.setDone(); |
| } |
| }); |
| } |
| |
| processingDone.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| result.setDone(nodesToInsert); |
| } |
| }); |
| |
| return result; |
| } |
| |
| @NotNull |
| protected DefaultMutableTreeNode createChildNode(final NodeDescriptor descriptor) { |
| return new ElementNode(this, descriptor); |
| } |
| |
| protected boolean canYield() { |
| return myCanYield && myYieldingUpdate.asBoolean(); |
| } |
| |
| public long getClearOnHideDelay() { |
| return myClearOnHideDelay > 0 ? myClearOnHideDelay : Registry.intValue("ide.tree.clearOnHideTime"); |
| } |
| |
| @NotNull |
| public ActionCallback getInitialized() { |
| return myInitialized; |
| } |
| |
| public ActionCallback getReady(@NotNull Object requestor) { |
| return myBusyObject.getReady(requestor); |
| } |
| |
| private ActionCallback _getReady() { |
| return getReady(this); |
| } |
| |
| private void addToUpdatingChildren(@NotNull DefaultMutableTreeNode node) { |
| synchronized (myUpdatingChildren) { |
| myUpdatingChildren.add(node); |
| } |
| } |
| |
| private void removeFromUpdatingChildren(@NotNull DefaultMutableTreeNode node) { |
| synchronized (myUpdatingChildren) { |
| myUpdatingChildren.remove(node); |
| } |
| } |
| |
| public boolean isUpdatingChildrenNow(DefaultMutableTreeNode node) { |
| synchronized (myUpdatingChildren) { |
| return myUpdatingChildren.contains(node); |
| } |
| } |
| |
| public boolean isParentUpdatingChildrenNow(@NotNull DefaultMutableTreeNode node) { |
| synchronized (myUpdatingChildren) { |
| DefaultMutableTreeNode eachParent = (DefaultMutableTreeNode)node.getParent(); |
| while (eachParent != null) { |
| if (myUpdatingChildren.contains(eachParent)) return true; |
| |
| eachParent = (DefaultMutableTreeNode)eachParent.getParent(); |
| } |
| |
| return false; |
| } |
| } |
| |
| boolean hasUpdatingChildrenNow() { |
| synchronized (myUpdatingChildren) { |
| return !myUpdatingChildren.isEmpty(); |
| } |
| } |
| |
| @NotNull |
| public Map<Object, List<NodeAction>> getNodeActions() { |
| return myNodeActions; |
| } |
| |
| @NotNull |
| public List<Object> getLoadedChildrenFor(Object element) { |
| List<Object> result = new ArrayList<Object>(); |
| |
| DefaultMutableTreeNode node = getNodeForElement(element, false); |
| if (node != null) { |
| for (int i = 0; i < node.getChildCount(); i++) { |
| TreeNode each = node.getChildAt(i); |
| if (isLoadingNode(each)) continue; |
| |
| result.add(getElementFor(each)); |
| } |
| } |
| |
| return result; |
| } |
| |
| public boolean hasNodesToUpdate() { |
| return getUpdater().hasNodesToUpdate(); |
| } |
| |
| @NotNull |
| public List<Object> getExpandedElements() { |
| final List<Object> result = new ArrayList<Object>(); |
| if (isReleased()) return result; |
| |
| final Enumeration<TreePath> enumeration = myTree.getExpandedDescendants(getPathFor(getRootNode())); |
| if (enumeration != null) { |
| while (enumeration.hasMoreElements()) { |
| TreePath each = enumeration.nextElement(); |
| Object eachElement = getElementFor(each.getLastPathComponent()); |
| if (eachElement != null) { |
| result.add(eachElement); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| @NotNull |
| public ActionCallback cancelUpdate() { |
| if (isReleased()) return new ActionCallback.Rejected(); |
| |
| setCancelRequested(true); |
| |
| final ActionCallback done = new ActionCallback(); |
| |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| if (isReleased()) { |
| done.setRejected(); |
| return; |
| } |
| |
| if (myResettingToReadyNow.get()) { |
| _getReady().notify(done); |
| } else if (isReady()) { |
| resetToReadyNow(); |
| done.setDone(); |
| } else { |
| if (isIdle() && hasPendingWork()) { |
| resetToReadyNow(); |
| done.setDone(); |
| } else { |
| _getReady().notify(done); |
| } |
| } |
| |
| maybeReady(); |
| } |
| }); |
| |
| if (isEdt() || isPassthroughMode()) { |
| maybeReady(); |
| } |
| |
| return done; |
| } |
| |
| private void setCancelRequested(boolean requested) { |
| boolean acquired = false; |
| try { |
| if (isUnitTestingMode()) { |
| acquireLock(); |
| acquired = true; |
| } |
| else { |
| acquired = attemptLock(); |
| } |
| myCancelRequest.set(requested); |
| } |
| catch (InterruptedException ignored) { |
| } |
| finally { |
| if (acquired) { |
| releaseLock(); |
| } |
| } |
| } |
| |
| private boolean attemptLock() throws InterruptedException { |
| return myStateLock.tryLock(Registry.intValue("ide.tree.uiLockAttempt"), TimeUnit.MILLISECONDS); |
| } |
| |
| private void acquireLock() { |
| myStateLock.lock(); |
| } |
| |
| private void releaseLock() { |
| myStateLock.unlock(); |
| } |
| |
| @NotNull |
| public ActionCallback batch(@NotNull final Progressive progressive) { |
| assertIsDispatchThread(); |
| |
| EmptyProgressIndicator indicator = new EmptyProgressIndicator(); |
| final ActionCallback callback = new ActionCallback(); |
| |
| myBatchIndicators.put(progressive, indicator); |
| myBatchCallbacks.put(progressive, callback); |
| |
| try { |
| progressive.run(indicator); |
| } |
| catch (ProcessCanceledException e) { |
| resetToReadyNow().doWhenProcessed(new Runnable() { |
| @Override |
| public void run() { |
| callback.setRejected(); |
| } |
| }); |
| return callback; |
| } |
| finally { |
| if (isReleased()) return new ActionCallback.Rejected(); |
| |
| _getReady().doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (myBatchIndicators.containsKey(progressive)) { |
| ProgressIndicator indicator = myBatchIndicators.remove(progressive); |
| myBatchCallbacks.remove(progressive); |
| |
| if (indicator.isCanceled()) { |
| callback.setRejected(); |
| } else { |
| callback.setDone(); |
| } |
| } else { |
| callback.setRejected(); |
| } |
| } |
| }); |
| |
| maybeReady(); |
| } |
| |
| |
| return callback; |
| } |
| |
| public boolean isCancelProcessed() { |
| return myCancelRequest.get() || myResettingToReadyNow.get(); |
| } |
| |
| public boolean isToPaintSelection() { |
| return isReady(true) || !mySelectionIsAdjusted; |
| } |
| |
| public boolean isReleaseRequested() { |
| return myReleaseRequested; |
| } |
| |
| public void executeUserRunnable(@NotNull Runnable runnable) { |
| try { |
| myUserRunnables.add(runnable); |
| runnable.run(); |
| } |
| finally { |
| myUserRunnables.remove(runnable); |
| } |
| } |
| |
| static class ElementNode extends DefaultMutableTreeNode { |
| |
| Set<Object> myElements = new HashSet<Object>(); |
| AbstractTreeUi myUi; |
| |
| ElementNode(AbstractTreeUi ui, NodeDescriptor descriptor) { |
| super(descriptor); |
| myUi = ui; |
| } |
| |
| @Override |
| public void insert(final MutableTreeNode newChild, final int childIndex) { |
| super.insert(newChild, childIndex); |
| final Object element = myUi.getElementFor(newChild); |
| if (element != null) { |
| myElements.add(element); |
| } |
| } |
| |
| @Override |
| public void remove(final int childIndex) { |
| final TreeNode node = getChildAt(childIndex); |
| super.remove(childIndex); |
| final Object element = myUi.getElementFor(node); |
| if (element != null) { |
| myElements.remove(element); |
| } |
| } |
| |
| boolean isValidChild(Object childElement) { |
| return myElements.contains(childElement); |
| } |
| |
| @Override |
| public String toString() { |
| return String.valueOf(getUserObject()); |
| } |
| } |
| |
| private boolean isUpdatingParent(DefaultMutableTreeNode kid) { |
| return getUpdatingParent(kid) != null; |
| } |
| |
| @Nullable |
| private DefaultMutableTreeNode getUpdatingParent(DefaultMutableTreeNode kid) { |
| DefaultMutableTreeNode eachParent = kid; |
| while (eachParent != null) { |
| if (isUpdatingChildrenNow(eachParent)) return eachParent; |
| eachParent = (DefaultMutableTreeNode)eachParent.getParent(); |
| } |
| |
| return null; |
| } |
| |
| private boolean isLoadedInBackground(Object element) { |
| return getLoadedInBackground(element) != null; |
| } |
| |
| private UpdateInfo getLoadedInBackground(Object element) { |
| synchronized (myLoadedInBackground) { |
| return myLoadedInBackground.get(element); |
| } |
| } |
| |
| private void addToLoadedInBackground(Object element, UpdateInfo info) { |
| synchronized (myLoadedInBackground) { |
| myLoadedInBackground.put(element, info); |
| } |
| } |
| |
| private void removeFromLoadedInBackground(final Object element) { |
| synchronized (myLoadedInBackground) { |
| myLoadedInBackground.remove(element); |
| } |
| } |
| |
| private boolean isLoadingInBackgroundNow() { |
| synchronized (myLoadedInBackground) { |
| return !myLoadedInBackground.isEmpty(); |
| } |
| } |
| |
| private boolean queueBackgroundUpdate(@NotNull final UpdateInfo updateInfo, @NotNull final DefaultMutableTreeNode node) { |
| assertIsDispatchThread(); |
| |
| final Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor()); |
| |
| UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor); |
| if (loaded != null) { |
| loaded.apply(updateInfo); |
| return false; |
| } |
| |
| addToLoadedInBackground(oldElementFromDescriptor, updateInfo); |
| |
| maybeSetBusyAndScheduleWaiterForReady(true, oldElementFromDescriptor); |
| |
| if (!isNodeBeingBuilt(node)) { |
| LoadingNode loadingNode = new LoadingNode(getLoadingNodeText()); |
| myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount()); |
| } |
| |
| removeFromUnbuilt(node); |
| |
| final Ref<LoadedChildren> children = new Ref<LoadedChildren>(); |
| final Ref<Object> elementFromDescriptor = new Ref<Object>(); |
| |
| final DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1]; |
| |
| final Runnable finalizeRunnable = new Runnable() { |
| @Override |
| public void run() { |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| if (isReleased()) return; |
| |
| removeLoading(node, false); |
| removeFromLoadedInBackground(elementFromDescriptor.get()); |
| removeFromLoadedInBackground(oldElementFromDescriptor); |
| |
| if (nodeToProcessActions[0] != null) { |
| processNodeActionsIfReady(nodeToProcessActions[0]); |
| } |
| } |
| }); |
| } |
| }; |
| |
| |
| Runnable buildRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (updateInfo.getPass().isExpired()) { |
| finalizeRunnable.run(); |
| return; |
| } |
| |
| if (!updateInfo.isDescriptorIsUpToDate()) { |
| update(updateInfo.getDescriptor(), true); |
| } |
| |
| if (!updateInfo.isUpdateChildren()) return; |
| |
| Object element = getElementFromDescriptor(updateInfo.getDescriptor()); |
| if (element == null) { |
| removeFromLoadedInBackground(oldElementFromDescriptor); |
| finalizeRunnable.run(); |
| return; |
| } |
| |
| elementFromDescriptor.set(element); |
| |
| Object[] loadedElements = getChildrenFor(getBuilder().getTreeStructureElement(updateInfo.getDescriptor())); |
| |
| final LoadedChildren loaded = new LoadedChildren(loadedElements); |
| for (final Object each : loadedElements) { |
| NodeDescriptor existingDesc = getDescriptorFrom(getNodeForElement(each, true)); |
| final NodeDescriptor eachChildDescriptor = existingDesc != null ? existingDesc : getTreeStructure().createDescriptor(each, updateInfo.getDescriptor()); |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).getResult()); |
| } |
| }); |
| } |
| |
| children.set(loaded); |
| } |
| |
| @NotNull |
| @NonNls |
| @Override |
| public String toString() { |
| return "runnable=" + oldElementFromDescriptor; |
| } |
| }; |
| |
| Runnable updateRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (updateInfo.getPass().isExpired()) { |
| finalizeRunnable.run(); |
| return; |
| } |
| |
| if (children.get() == null) { |
| finalizeRunnable.run(); |
| return; |
| } |
| |
| if (isRerunNeeded(updateInfo.getPass())) { |
| removeFromLoadedInBackground(elementFromDescriptor.get()); |
| getUpdater().requeue(updateInfo.getPass()); |
| return; |
| } |
| |
| removeFromLoadedInBackground(elementFromDescriptor.get()); |
| |
| if (myUnbuiltNodes.contains(node)) { |
| Pair<Boolean, LoadedChildren> unbuilt = |
| processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()), |
| children.get()); |
| if (unbuilt.getFirst()) { |
| nodeToProcessActions[0] = node; |
| return; |
| } |
| } |
| |
| ActionCallback callback = updateNodeChildren(node, updateInfo.getPass(), children.get(), |
| true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(), true, true); |
| callback.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (isRerunNeeded(updateInfo.getPass())) { |
| getUpdater().requeue(updateInfo.getPass()); |
| return; |
| } |
| |
| Object element = elementFromDescriptor.get(); |
| |
| if (element != null) { |
| removeLoading(node, false); |
| nodeToProcessActions[0] = node; |
| } |
| } |
| }); |
| } |
| }; |
| queueToBackground(buildRunnable, updateRunnable).doWhenProcessed(finalizeRunnable).doWhenRejected(new Runnable() { |
| @Override |
| public void run() { |
| updateInfo.getPass().expire(); |
| } |
| }); |
| |
| return true; |
| } |
| |
| private boolean isExpanded(@NotNull DefaultMutableTreeNode node, boolean isExpanded) { |
| return isExpanded || myTree.isExpanded(getPathFor(node)); |
| } |
| |
| private void removeLoading(@NotNull DefaultMutableTreeNode parent, boolean forced) { |
| if (!forced && myUnbuiltNodes.contains(parent) && !myCancelledBuild.containsKey(parent)) { |
| return; |
| } |
| |
| boolean reallyRemoved = false; |
| for (int i = 0; i < parent.getChildCount(); i++) { |
| TreeNode child = parent.getChildAt(i); |
| if (removeIfLoading(child)) { |
| reallyRemoved = true; |
| i--; |
| } |
| } |
| |
| if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) { |
| insertLoadingNode(parent, false); |
| reallyRemoved = false; |
| } |
| |
| maybeReady(); |
| if (reallyRemoved) { |
| myTreeModel.nodeStructureChanged(parent); |
| } |
| } |
| |
| private void processNodeActionsIfReady(@NotNull final DefaultMutableTreeNode node) { |
| assertIsDispatchThread(); |
| |
| if (isNodeBeingBuilt(node)) return; |
| |
| final Object o = node.getUserObject(); |
| if (!(o instanceof NodeDescriptor)) return; |
| |
| |
| if (isYeildingNow()) { |
| myPendingNodeActions.add(node); |
| return; |
| } |
| |
| final Object element = getBuilder().getTreeStructureElement((NodeDescriptor)o); |
| |
| boolean childrenReady = !isLoadedInBackground(element) && !isUpdatingChildrenNow(node); |
| |
| processActions(node, element, myNodeActions, childrenReady ? myNodeChildrenActions : null); |
| if (childrenReady) { |
| processActions(node, element, myNodeChildrenActions, null); |
| } |
| |
| if (!isUpdatingParent(node) && !isWorkerBusy()) { |
| final UpdaterTreeState state = myUpdaterState; |
| if (myNodeActions.isEmpty() && state != null && !state.isProcessingNow()) { |
| if (canInitiateNewActivity()) { |
| if (!state.restore(childrenReady ? node : null)) { |
| setUpdaterState(state); |
| } |
| } |
| } |
| } |
| |
| maybeReady(); |
| } |
| |
| |
| private static void processActions(DefaultMutableTreeNode node, |
| Object element, |
| @NotNull final Map<Object, List<NodeAction>> nodeActions, |
| @Nullable final Map<Object, List<NodeAction>> secondaryNodeAction) { |
| final List<NodeAction> actions = nodeActions.get(element); |
| if (actions != null) { |
| nodeActions.remove(element); |
| |
| List<NodeAction> secondary = secondaryNodeAction != null ? secondaryNodeAction.get(element) : null; |
| for (NodeAction each : actions) { |
| if (secondary != null && secondary.contains(each)) { |
| secondary.remove(each); |
| } |
| each.onReady(node); |
| } |
| } |
| } |
| |
| |
| private boolean canSmartExpand(DefaultMutableTreeNode node, boolean canSmartExpand) { |
| if (!canInitiateNewActivity()) return false; |
| if (!getBuilder().isSmartExpand()) return false; |
| |
| boolean smartExpand = !myNotForSmartExpand.contains(node) && canSmartExpand; |
| return smartExpand && validateAutoExpand(true, getElementFor(node)); |
| } |
| |
| private void processSmartExpand(@NotNull final DefaultMutableTreeNode node, final boolean canSmartExpand, boolean forced) { |
| if (!canInitiateNewActivity()) return; |
| if (!getBuilder().isSmartExpand()) return; |
| |
| boolean can = canSmartExpand(node, canSmartExpand); |
| |
| if (!can && !forced) return; |
| |
| if (isNodeBeingBuilt(node) && !forced) { |
| addNodeAction(getElementFor(node), new NodeAction() { |
| @Override |
| public void onReady(@NotNull DefaultMutableTreeNode node) { |
| processSmartExpand(node, canSmartExpand, true); |
| } |
| }, true); |
| } |
| else { |
| TreeNode child = getChildForSmartExpand(node); |
| if (child != null) { |
| final TreePath childPath = new TreePath(node.getPath()).pathByAddingChild(child); |
| processInnerChange(new Runnable() { |
| @Override |
| public void run() { |
| myTree.expandPath(childPath); |
| } |
| }); |
| } |
| } |
| } |
| |
| @Nullable |
| private static TreeNode getChildForSmartExpand(@NotNull DefaultMutableTreeNode node) { |
| int realChildCount = 0; |
| TreeNode nodeToExpand = null; |
| |
| for (int i = 0; i < node.getChildCount(); i++) { |
| TreeNode eachChild = node.getChildAt(i); |
| |
| if (!isLoadingNode(eachChild)) { |
| realChildCount++; |
| if (nodeToExpand == null) { |
| nodeToExpand = eachChild; |
| } |
| } |
| |
| if (realChildCount > 1) { |
| nodeToExpand = null; |
| break; |
| } |
| } |
| |
| return nodeToExpand; |
| } |
| |
| public static boolean isLoadingChildrenFor(final Object nodeObject) { |
| if (!(nodeObject instanceof DefaultMutableTreeNode)) return false; |
| |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject; |
| |
| int loadingNodes = 0; |
| for (int i = 0; i < Math.min(node.getChildCount(), 2); i++) { |
| TreeNode child = node.getChildAt(i); |
| if (isLoadingNode(child)) { |
| loadingNodes++; |
| } |
| } |
| return loadingNodes > 0 && loadingNodes == node.getChildCount(); |
| } |
| |
| public boolean isParentLoadingInBackground(Object nodeObject) { |
| return getParentLoadingInBackground(nodeObject) != null; |
| } |
| |
| @Nullable |
| private DefaultMutableTreeNode getParentLoadingInBackground(Object nodeObject) { |
| if (!(nodeObject instanceof DefaultMutableTreeNode)) return null; |
| |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObject; |
| |
| TreeNode eachParent = node.getParent(); |
| |
| while (eachParent != null) { |
| eachParent = eachParent.getParent(); |
| if (eachParent instanceof DefaultMutableTreeNode) { |
| final Object eachElement = getElementFor((DefaultMutableTreeNode)eachParent); |
| if (isLoadedInBackground(eachElement)) return (DefaultMutableTreeNode)eachParent; |
| } |
| } |
| |
| return null; |
| } |
| |
| protected static String getLoadingNodeText() { |
| return IdeBundle.message("progress.searching"); |
| } |
| |
| @NotNull |
| private ActionCallback processExistingNode(@NotNull final DefaultMutableTreeNode childNode, |
| final NodeDescriptor childDescriptor, |
| @NotNull final DefaultMutableTreeNode parentNode, |
| @NotNull final MutualMap<Object, Integer> elementToIndexMap, |
| @NotNull final TreeUpdatePass pass, |
| final boolean canSmartExpand, |
| final boolean forceUpdate, |
| @Nullable LoadedChildren parentPreloadedChildren) { |
| if (pass.isExpired()) { |
| return new ActionCallback.Rejected(); |
| } |
| |
| if (childDescriptor == null) { |
| pass.expire(); |
| return new ActionCallback.Rejected(); |
| } |
| final Object oldElement = getElementFromDescriptor(childDescriptor); |
| if (oldElement == null) { |
| pass.expire(); |
| return new ActionCallback.Rejected(); |
| } |
| |
| AsyncResult<Boolean> update = new AsyncResult<Boolean>(); |
| if (parentPreloadedChildren != null && parentPreloadedChildren.getDescriptor(oldElement) != null) { |
| update.setDone(parentPreloadedChildren.isUpdated(oldElement)); |
| } |
| else { |
| update = update(childDescriptor, false); |
| } |
| |
| final ActionCallback result = new ActionCallback(); |
| final Ref<NodeDescriptor> childDesc = new Ref<NodeDescriptor>(childDescriptor); |
| |
| update.doWhenDone(new Consumer<Boolean>() { |
| @Override |
| public void consume(Boolean isChanged) { |
| final AtomicBoolean changes = new AtomicBoolean(isChanged); |
| final AtomicBoolean forceRemapping = new AtomicBoolean(); |
| final Ref<Object> newElement = new Ref<Object>(getElementFromDescriptor(childDesc.get())); |
| |
| final Integer index = newElement.get() == null ? null : elementToIndexMap.getValue(getBuilder().getTreeStructureElement(childDesc.get())); |
| final AsyncResult<Boolean> updateIndexDone = new AsyncResult<Boolean>(); |
| if (index == null) { |
| updateIndexDone.setDone(); |
| } |
| else { |
| final Object elementFromMap = elementToIndexMap.getKey(index); |
| if (elementFromMap != newElement.get() && elementFromMap.equals(newElement.get())) { |
| if (isInStructure(elementFromMap) && isInStructure(newElement.get())) { |
| if (parentNode.getUserObject() instanceof NodeDescriptor) { |
| final NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode); |
| childDesc.set(getTreeStructure().createDescriptor(elementFromMap, parentDescriptor)); |
| NodeDescriptor oldDesc = getDescriptorFrom(childNode); |
| if (oldDesc != null) { |
| childDesc.get().applyFrom(oldDesc); |
| } |
| |
| childNode.setUserObject(childDesc.get()); |
| newElement.set(elementFromMap); |
| forceRemapping.set(true); |
| update(childDesc.get(), false).doWhenDone(new Consumer<Boolean>() { |
| @Override |
| public void consume(Boolean isChanged) { |
| changes.set(isChanged); |
| updateIndexDone.setDone(isChanged); |
| } |
| }); |
| } |
| } |
| else { |
| updateIndexDone.setDone(changes.get()); |
| } |
| } |
| else { |
| updateIndexDone.setDone(changes.get()); |
| } |
| |
| updateIndexDone.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (childDesc.get().getIndex() != index.intValue()) { |
| changes.set(true); |
| } |
| childDesc.get().setIndex(index.intValue()); |
| } |
| }); |
| } |
| |
| updateIndexDone.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (!oldElement.equals(newElement.get()) || forceRemapping.get()) { |
| removeMapping(oldElement, childNode, newElement.get()); |
| Object newE = newElement.get(); |
| if (newE != null) { |
| createMapping(newE, childNode); |
| } |
| NodeDescriptor parentDescriptor = getDescriptorFrom(parentNode); |
| if (parentDescriptor != null) { |
| parentDescriptor.setChildrenSortingStamp(-1); |
| } |
| } |
| |
| if (index == null) { |
| int selectedIndex = -1; |
| if (TreeBuilderUtil.isNodeOrChildSelected(myTree, childNode)) { |
| selectedIndex = parentNode.getIndex(childNode); |
| } |
| |
| if (childNode.getParent() instanceof DefaultMutableTreeNode) { |
| final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)childNode.getParent(); |
| if (myTree.isExpanded(new TreePath(parent.getPath()))) { |
| if (parent.getChildCount() == 1 && parent.getChildAt(0) == childNode) { |
| insertLoadingNode(parent, false); |
| } |
| } |
| } |
| |
| Object disposedElement = getElementFor(childNode); |
| |
| removeNodeFromParent(childNode, selectedIndex >= 0); |
| disposeNode(childNode); |
| |
| adjustSelectionOnChildRemove(parentNode, selectedIndex, disposedElement); |
| result.setDone(); |
| } |
| else { |
| elementToIndexMap.remove(getBuilder().getTreeStructureElement(childDesc.get())); |
| ActionCallback callback = updateNodeChildren(childNode, pass, null, false, canSmartExpand, forceUpdate, true, true); |
| callback.doWhenDone(result.createSetDoneRunnable()); |
| } |
| } |
| }); |
| } |
| }); |
| |
| |
| return result; |
| } |
| |
| private void adjustSelectionOnChildRemove(@NotNull DefaultMutableTreeNode parentNode, int selectedIndex, Object disposedElement) { |
| if (selectedIndex >= 0 && !getSelectedElements().isEmpty()) return; |
| |
| DefaultMutableTreeNode node = getNodeForElement(disposedElement, false); |
| if (node != null && isValidForSelectionAdjusting(node)) { |
| Object newElement = getElementFor(node); |
| addSelectionPath(getPathFor(node), true, getExpiredElementCondition(newElement), disposedElement); |
| return; |
| } |
| |
| |
| if (selectedIndex >= 0) { |
| if (parentNode.getChildCount() > 0) { |
| if (parentNode.getChildCount() > selectedIndex) { |
| TreeNode newChildNode = parentNode.getChildAt(selectedIndex); |
| if (isValidForSelectionAdjusting(newChildNode)) { |
| addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChildNode)), true, getExpiredElementCondition(disposedElement), |
| disposedElement); |
| } |
| } |
| else { |
| TreeNode newChild = parentNode.getChildAt(parentNode.getChildCount() - 1); |
| if (isValidForSelectionAdjusting(newChild)) { |
| addSelectionPath(new TreePath(myTreeModel.getPathToRoot(newChild)), true, getExpiredElementCondition(disposedElement), |
| disposedElement); |
| } |
| } |
| } |
| else { |
| addSelectionPath(new TreePath(myTreeModel.getPathToRoot(parentNode)), true, getExpiredElementCondition(disposedElement), |
| disposedElement); |
| } |
| } |
| } |
| |
| private boolean isValidForSelectionAdjusting(@NotNull TreeNode node) { |
| if (!myTree.isRootVisible() && getRootNode() == node) return false; |
| |
| if (isLoadingNode(node)) return true; |
| |
| final Object elementInTree = getElementFor(node); |
| if (elementInTree == null) return false; |
| |
| final TreeNode parentNode = node.getParent(); |
| final Object parentElementInTree = getElementFor(parentNode); |
| if (parentElementInTree == null) return false; |
| |
| final Object parentElement = getTreeStructure().getParentElement(elementInTree); |
| |
| return parentElementInTree.equals(parentElement); |
| } |
| |
| @NotNull |
| public Condition getExpiredElementCondition(final Object element) { |
| return new Condition() { |
| @Override |
| public boolean value(final Object o) { |
| return isInStructure(element); |
| } |
| }; |
| } |
| |
| private void addSelectionPath(@NotNull final TreePath path, |
| final boolean isAdjustedSelection, |
| final Condition isExpiredAdjustement, |
| @Nullable final Object adjustmentCause) { |
| processInnerChange(new Runnable() { |
| @Override |
| public void run() { |
| TreePath toSelect = null; |
| |
| if (isLoadingNode(path.getLastPathComponent())) { |
| final TreePath parentPath = path.getParentPath(); |
| if (parentPath != null) { |
| if (isValidForSelectionAdjusting((TreeNode)parentPath.getLastPathComponent())) { |
| toSelect = parentPath; |
| } |
| else { |
| toSelect = null; |
| } |
| } |
| } |
| else { |
| toSelect = path; |
| } |
| |
| if (toSelect != null) { |
| mySelectionIsAdjusted = isAdjustedSelection; |
| |
| myTree.addSelectionPath(toSelect); |
| |
| if (isAdjustedSelection && myUpdaterState != null) { |
| final Object toSelectElement = getElementFor(toSelect.getLastPathComponent()); |
| myUpdaterState.addAdjustedSelection(toSelectElement, isExpiredAdjustement, adjustmentCause); |
| } |
| } |
| } |
| }); |
| } |
| |
| @NotNull |
| private static TreePath getPathFor(@NotNull TreeNode node) { |
| if (node instanceof DefaultMutableTreeNode) { |
| return new TreePath(((DefaultMutableTreeNode)node).getPath()); |
| } |
| else { |
| List<TreeNode> nodes = new ArrayList<TreeNode>(); |
| TreeNode eachParent = node; |
| while (eachParent != null) { |
| nodes.add(eachParent); |
| eachParent = eachParent.getParent(); |
| } |
| |
| return new TreePath(ArrayUtil.toObjectArray(nodes)); |
| } |
| } |
| |
| |
| private void removeNodeFromParent(@NotNull final MutableTreeNode node, final boolean willAdjustSelection) { |
| processInnerChange(new Runnable() { |
| @Override |
| public void run() { |
| if (willAdjustSelection) { |
| final TreePath path = getPathFor(node); |
| if (myTree.isPathSelected(path)) { |
| myTree.removeSelectionPath(path); |
| } |
| } |
| |
| if (node.getParent() != null) { |
| myTreeModel.removeNodeFromParent(node); |
| } |
| } |
| }); |
| } |
| |
| private void expandPath(@NotNull final TreePath path, final boolean canSmartExpand) { |
| processInnerChange(new Runnable() { |
| @Override |
| public void run() { |
| if (path.getLastPathComponent() instanceof DefaultMutableTreeNode) { |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); |
| if (node.getChildCount() > 0 && !myTree.isExpanded(path)) { |
| if (!canSmartExpand) { |
| myNotForSmartExpand.add(node); |
| } |
| try { |
| myRequestedExpand = path; |
| myTree.expandPath(path); |
| processSmartExpand(node, canSmartExpand, false); |
| } |
| finally { |
| myNotForSmartExpand.remove(node); |
| myRequestedExpand = null; |
| } |
| } |
| else { |
| processNodeActionsIfReady(node); |
| } |
| } |
| } |
| }); |
| } |
| |
| private void processInnerChange(Runnable runnable) { |
| if (myUpdaterState == null) { |
| setUpdaterState(new UpdaterTreeState(this)); |
| } |
| |
| myUpdaterState.process(runnable); |
| } |
| |
| private boolean isInnerChange() { |
| return myUpdaterState != null && myUpdaterState.isProcessingNow() && myUserRunnables.isEmpty(); |
| } |
| |
| private void makeLoadingOrLeafIfNoChildren(@NotNull final DefaultMutableTreeNode node) { |
| TreePath path = getPathFor(node); |
| |
| insertLoadingNode(node, true); |
| |
| final NodeDescriptor descriptor = getDescriptorFrom(node); |
| if (descriptor == null) return; |
| |
| descriptor.setChildrenSortingStamp(-1); |
| |
| if (getBuilder().isAlwaysShowPlus(descriptor)) return; |
| |
| |
| TreePath parentPath = path.getParentPath(); |
| if (myTree.isVisible(path) || parentPath != null && myTree.isExpanded(parentPath)) { |
| if (myTree.isExpanded(path)) { |
| addSubtreeToUpdate(node); |
| } |
| else { |
| insertLoadingNode(node, false); |
| } |
| } |
| } |
| |
| |
| private boolean isValid(@Nullable NodeDescriptor descriptor) { |
| return descriptor != null && isValid(getElementFromDescriptor(descriptor)); |
| } |
| |
| private boolean isValid(Object element) { |
| if (element instanceof ValidateableNode) { |
| if (!((ValidateableNode)element).isValid()) return false; |
| } |
| return getBuilder().validateNode(element); |
| } |
| |
| private void insertLoadingNode(final DefaultMutableTreeNode node, boolean addToUnbuilt) { |
| if (!isLoadingChildrenFor(node)) { |
| myTreeModel.insertNodeInto(new LoadingNode(), node, 0); |
| } |
| |
| if (addToUnbuilt) { |
| addToUnbuilt(node); |
| } |
| } |
| |
| |
| @NotNull |
| private ActionCallback queueToBackground(@NotNull final Runnable bgBuildAction, @Nullable final Runnable edtPostRunnable) { |
| if (!canInitiateNewActivity()) return new ActionCallback.Rejected(); |
| final ActionCallback result = new ActionCallback(); |
| final AtomicBoolean fail = new AtomicBoolean(); |
| final Runnable finalizer = new Runnable() { |
| @Override |
| public void run() { |
| if (fail.get()) { |
| result.setRejected(); |
| } |
| else { |
| result.setDone(); |
| } |
| } |
| }; |
| |
| registerWorkerTask(bgBuildAction); |
| |
| final Runnable pooledThreadWithProgressRunnable = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| final AbstractTreeBuilder builder = getBuilder(); |
| |
| if (!canInitiateNewActivity()) { |
| throw new ProcessCanceledException(); |
| } |
| |
| builder.runBackgroundLoading(new Runnable() { |
| @Override |
| public void run() { |
| assertNotDispatchThread(); |
| try { |
| if (!canInitiateNewActivity()) { |
| throw new ProcessCanceledException(); |
| } |
| |
| execute(bgBuildAction); |
| |
| if (edtPostRunnable != null) { |
| builder.updateAfterLoadedInBackground(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| assertIsDispatchThread(); |
| if (!canInitiateNewActivity()) { |
| throw new ProcessCanceledException(); |
| } |
| |
| execute(edtPostRunnable); |
| |
| } |
| catch (ProcessCanceledException e) { |
| fail.set(true); |
| cancelUpdate(); |
| } |
| finally { |
| unregisterWorkerTask(bgBuildAction, finalizer); |
| } |
| } |
| }); |
| } |
| else { |
| unregisterWorkerTask(bgBuildAction, finalizer); |
| } |
| } |
| catch (ProcessCanceledException e) { |
| fail.set(true); |
| unregisterWorkerTask(bgBuildAction, finalizer); |
| cancelUpdate(); |
| } |
| catch (Throwable t) { |
| unregisterWorkerTask(bgBuildAction, finalizer); |
| throw new RuntimeException(t); |
| } |
| } |
| }); |
| } |
| catch (ProcessCanceledException e) { |
| unregisterWorkerTask(bgBuildAction, finalizer); |
| cancelUpdate(); |
| } |
| } |
| }; |
| |
| Runnable pooledThreadRunnable = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| if (myProgress != null) { |
| ProgressManager.getInstance().runProcess(pooledThreadWithProgressRunnable, myProgress); |
| } |
| else { |
| execute(pooledThreadWithProgressRunnable); |
| } |
| } |
| catch (ProcessCanceledException e) { |
| fail.set(true); |
| unregisterWorkerTask(bgBuildAction, finalizer); |
| cancelUpdate(); |
| } |
| } |
| }; |
| |
| if (isPassthroughMode()) { |
| execute(pooledThreadRunnable); |
| } |
| else { |
| if (myWorker == null || myWorker.isDisposed()) { |
| myWorker = new WorkerThread("AbstractTreeBuilder.Worker", 1); |
| myWorker.start(); |
| myWorker.addTaskFirst(pooledThreadRunnable); |
| myWorker.dispose(false); |
| } |
| else { |
| myWorker.addTaskFirst(pooledThreadRunnable); |
| } |
| } |
| |
| |
| return result; |
| } |
| |
| private void registerWorkerTask(@NotNull Runnable runnable) { |
| synchronized (myActiveWorkerTasks) { |
| myActiveWorkerTasks.add(runnable); |
| } |
| } |
| |
| private void unregisterWorkerTask(@NotNull Runnable runnable, @Nullable Runnable finalizeRunnable) { |
| boolean wasRemoved; |
| |
| synchronized (myActiveWorkerTasks) { |
| wasRemoved = myActiveWorkerTasks.remove(runnable); |
| } |
| |
| if (wasRemoved && finalizeRunnable != null) { |
| finalizeRunnable.run(); |
| } |
| |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| maybeReady(); |
| } |
| }); |
| } |
| |
| public boolean isWorkerBusy() { |
| synchronized (myActiveWorkerTasks) { |
| return !myActiveWorkerTasks.isEmpty(); |
| } |
| } |
| |
| private void clearWorkerTasks() { |
| synchronized (myActiveWorkerTasks) { |
| myActiveWorkerTasks.clear(); |
| } |
| } |
| |
| private void updateNodeImageAndPosition(@NotNull final DefaultMutableTreeNode node, boolean updatePosition, boolean nodeChanged) { |
| if (!(node.getUserObject() instanceof NodeDescriptor)) return; |
| NodeDescriptor descriptor = getDescriptorFrom(node); |
| if (getElementFromDescriptor(descriptor) == null) return; |
| |
| if (updatePosition) { |
| DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent(); |
| if (parentNode == null) { |
| nodeChanged(node); |
| } |
| else { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| int oldIndex = parentNode.getIndex(node); |
| int newIndex = oldIndex; |
| if (isLoadingChildrenFor(node.getParent()) || getBuilder().isChildrenResortingNeeded(descriptor)) { |
| List<TreeNode> children = new ArrayList<TreeNode>(parentNode.getChildCount()); |
| for (int i = 0; i < parentNode.getChildCount(); i++) { |
| TreeNode child = parentNode.getChildAt(i); |
| LOG.assertTrue(child != null); |
| children.add(child); |
| } |
| sortChildren(node, children, true, false); |
| newIndex = children.indexOf(node); |
| } |
| |
| if (oldIndex != newIndex) { |
| List<Object> pathsToExpand = new ArrayList<Object>(); |
| List<Object> selectionPaths = new ArrayList<Object>(); |
| TreeBuilderUtil.storePaths(getBuilder(), node, pathsToExpand, selectionPaths, false); |
| removeNodeFromParent(node, false); |
| myTreeModel.insertNodeInto(node, parentNode, newIndex); |
| TreeBuilderUtil.restorePaths(getBuilder(), pathsToExpand, selectionPaths, false); |
| } |
| else { |
| nodeChanged(node); |
| } |
| } |
| } |
| else if (nodeChanged) { |
| nodeChanged(node); |
| } |
| } |
| |
| private void nodeChanged(final DefaultMutableTreeNode node) { |
| invokeLaterIfNeeded(true, new Runnable() { |
| @Override |
| public void run() { |
| myTreeModel.nodeChanged(node); |
| } |
| }); |
| } |
| |
| public DefaultTreeModel getTreeModel() { |
| return myTreeModel; |
| } |
| |
| private void insertNodesInto(@NotNull final List<TreeNode> toInsert, @NotNull final DefaultMutableTreeNode parentNode) { |
| sortChildren(parentNode, toInsert, false, true); |
| final List<TreeNode> all = new ArrayList<TreeNode>(toInsert.size() + parentNode.getChildCount()); |
| all.addAll(toInsert); |
| all.addAll(TreeUtil.childrenToArray(parentNode)); |
| |
| if (!toInsert.isEmpty()) { |
| sortChildren(parentNode, all, true, true); |
| |
| int[] newNodeIndices = new int[toInsert.size()]; |
| int eachNewNodeIndex = 0; |
| TreeMap<Integer, TreeNode> insertSet = new TreeMap<Integer, TreeNode>(); |
| for (int i = 0; i < toInsert.size(); i++) { |
| TreeNode eachNewNode = toInsert.get(i); |
| while (all.get(eachNewNodeIndex) != eachNewNode) { |
| eachNewNodeIndex++; |
| } |
| newNodeIndices[i] = eachNewNodeIndex; |
| insertSet.put(eachNewNodeIndex, eachNewNode); |
| } |
| |
| for (Integer eachIndex : insertSet.keySet()) { |
| TreeNode eachNode = insertSet.get(eachIndex); |
| parentNode.insert((MutableTreeNode)eachNode, eachIndex); |
| } |
| |
| myTreeModel.nodesWereInserted(parentNode, newNodeIndices); |
| } |
| else { |
| List<TreeNode> before = new ArrayList<TreeNode>(); |
| before.addAll(all); |
| |
| sortChildren(parentNode, all, true, false); |
| if (!before.equals(all)) { |
| processInnerChange(new Runnable() { |
| @Override |
| public void run() { |
| Enumeration<TreePath> expanded = getTree().getExpandedDescendants(getPathFor(parentNode)); |
| TreePath[] selected = getTree().getSelectionModel().getSelectionPaths(); |
| |
| parentNode.removeAllChildren(); |
| for (TreeNode each : all) { |
| parentNode.add((MutableTreeNode)each); |
| } |
| myTreeModel.nodeStructureChanged(parentNode); |
| |
| if (expanded != null) { |
| while (expanded.hasMoreElements()) { |
| expandSilently(expanded.nextElement()); |
| } |
| } |
| |
| if (selected != null) { |
| for (TreePath each : selected) { |
| if (!getTree().getSelectionModel().isPathSelected(each)) { |
| addSelectionSilently(each); |
| } |
| } |
| } |
| } |
| }); |
| } |
| } |
| } |
| |
| private void sortChildren(@NotNull DefaultMutableTreeNode node, @NotNull List<TreeNode> children, boolean updateStamp, boolean forceSort) { |
| NodeDescriptor descriptor = getDescriptorFrom(node); |
| assert descriptor != null; |
| |
| if (descriptor.getChildrenSortingStamp() >= getComparatorStamp() && !forceSort) return; |
| if (!children.isEmpty()) { |
| getBuilder().sortChildren(myNodeComparator, node, (ArrayList<TreeNode>)children); |
| } |
| |
| if (updateStamp) { |
| descriptor.setChildrenSortingStamp(getComparatorStamp()); |
| } |
| } |
| |
| public Comparator getNodeDescriptorComparator() { |
| return myNodeDescriptorComparator; |
| } |
| |
| private void disposeNode(@NotNull DefaultMutableTreeNode node) { |
| TreeNode parent = node.getParent(); |
| if (parent instanceof DefaultMutableTreeNode) { |
| addToUnbuilt((DefaultMutableTreeNode)parent); |
| } |
| |
| if (node.getChildCount() > 0) { |
| for (DefaultMutableTreeNode _node = (DefaultMutableTreeNode)node.getFirstChild(); _node != null; _node = _node.getNextSibling()) { |
| disposeNode(_node); |
| } |
| } |
| |
| removeFromUpdatingChildren(node); |
| removeFromUnbuilt(node); |
| removeFromCancelled(node); |
| |
| if (isLoadingNode(node)) return; |
| NodeDescriptor descriptor = getDescriptorFrom(node); |
| if (descriptor == null) return; |
| final Object element = getElementFromDescriptor(descriptor); |
| if (element != null) { |
| removeMapping(element, node, null); |
| } |
| myAutoExpandRoots.remove(element); |
| node.setUserObject(null); |
| node.removeAllChildren(); |
| } |
| |
| public boolean addSubtreeToUpdate(@NotNull final DefaultMutableTreeNode root) { |
| return addSubtreeToUpdate(root, true); |
| } |
| |
| public boolean addSubtreeToUpdate(@NotNull final DefaultMutableTreeNode root, boolean updateStructure) { |
| return addSubtreeToUpdate(root, null, updateStructure); |
| } |
| |
| public boolean addSubtreeToUpdate(@NotNull final DefaultMutableTreeNode root, final Runnable runAfterUpdate) { |
| return addSubtreeToUpdate(root, runAfterUpdate, true); |
| } |
| |
| public boolean addSubtreeToUpdate(@NotNull final DefaultMutableTreeNode root, @Nullable final Runnable runAfterUpdate, final boolean updateStructure) { |
| Object element = getElementFor(root); |
| if (element != null && getTreeStructure().isAlwaysLeaf(element)) { |
| removeFromUnbuilt(root); |
| removeLoading(root, true); |
| |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| getUpdater().runAfterUpdate(runAfterUpdate); |
| getUpdater().addSubtreeToUpdate(new TreeUpdatePass(root).setUpdateChildren(false)); |
| } |
| }); |
| return false; |
| } |
| else { |
| execute(new Runnable() { |
| @Override |
| public void run() { |
| getUpdater().runAfterUpdate(runAfterUpdate); |
| getUpdater().addSubtreeToUpdate(new TreeUpdatePass(root).setUpdateStructure(updateStructure).setUpdateStamp(-1)); |
| } |
| }); |
| |
| return true; |
| } } |
| |
| public boolean wasRootNodeInitialized() { |
| return myRootNodeWasQueuedToInitialize && myRootNodeInitialized; |
| } |
| |
| public void select(@NotNull final Object[] elements, @Nullable final Runnable onDone) { |
| select(elements, onDone, false); |
| } |
| |
| public void select(@NotNull final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) { |
| select(elements, onDone, addToSelection, false); |
| } |
| |
| public void select(@NotNull final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection, boolean deferred) { |
| _select(elements, onDone, addToSelection, true, false, true, deferred, false, false); |
| } |
| |
| void _select(@NotNull final Object[] elements, |
| final Runnable onDone, |
| final boolean addToSelection, |
| final boolean checkCurrentSelection, |
| final boolean checkIfInStructure) { |
| |
| _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, true, false, false, false); |
| } |
| |
| void _select(@NotNull final Object[] elements, |
| final Runnable onDone, |
| final boolean addToSelection, |
| final boolean checkCurrentSelection, |
| final boolean checkIfInStructure, |
| final boolean scrollToVisible) { |
| |
| _select(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, false, false, false); |
| } |
| |
| public void userSelect(@NotNull final Object[] elements, final Runnable onDone, final boolean addToSelection, boolean scroll) { |
| _select(elements, onDone, addToSelection, true, false, scroll, false, true, true); |
| } |
| |
| void _select(@NotNull final Object[] elements, |
| final Runnable onDone, |
| final boolean addToSelection, |
| final boolean checkCurrentSelection, |
| final boolean checkIfInStructure, |
| final boolean scrollToVisible, |
| final boolean deferred, |
| final boolean canSmartExpand, |
| final boolean mayQueue) { |
| |
| assertIsDispatchThread(); |
| |
| AbstractTreeUpdater updater = getUpdater(); |
| if (mayQueue && updater != null) { |
| updater.queueSelection( |
| new SelectionRequest(elements, onDone, addToSelection, checkCurrentSelection, checkIfInStructure, scrollToVisible, deferred, |
| canSmartExpand)); |
| return; |
| } |
| |
| |
| boolean willAffectSelection = elements.length > 0 || elements.length == 0 && addToSelection; |
| if (!willAffectSelection) { |
| runDone(onDone); |
| maybeReady(); |
| return; |
| } |
| |
| final boolean oldCanProcessDeferredSelection = myCanProcessDeferredSelections; |
| |
| if (!deferred && wasRootNodeInitialized()) { |
| _getReady().doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| myCanProcessDeferredSelections = false; |
| } |
| }); |
| } |
| |
| if (!checkDeferred(deferred, onDone)) return; |
| |
| if (!deferred && oldCanProcessDeferredSelection && !myCanProcessDeferredSelections) { |
| if (!addToSelection) { |
| getTree().clearSelection(); |
| } |
| } |
| |
| |
| runDone(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| if (!checkDeferred(deferred, onDone)) return; |
| |
| final Set<Object> currentElements = getSelectedElements(); |
| |
| if (checkCurrentSelection && !currentElements.isEmpty() && elements.length == currentElements.size()) { |
| boolean runSelection = false; |
| for (Object eachToSelect : elements) { |
| if (!currentElements.contains(eachToSelect)) { |
| runSelection = true; |
| break; |
| } |
| } |
| |
| if (!runSelection) { |
| if (elements.length > 0) { |
| selectVisible(elements[0], onDone, true, true, scrollToVisible); |
| } |
| return; |
| } |
| } |
| |
| clearSelection(); |
| Set<Object> toSelect = new THashSet<Object>(); |
| ContainerUtil.addAllNotNull(toSelect, elements); |
| if (addToSelection) { |
| ContainerUtil.addAllNotNull(toSelect, currentElements); |
| } |
| |
| if (checkIfInStructure) { |
| final Iterator<Object> allToSelect = toSelect.iterator(); |
| while (allToSelect.hasNext()) { |
| Object each = allToSelect.next(); |
| if (!isInStructure(each)) { |
| allToSelect.remove(); |
| } |
| } |
| } |
| |
| final Object[] elementsToSelect = ArrayUtil.toObjectArray(toSelect); |
| |
| if (wasRootNodeInitialized()) { |
| final int[] originalRows = myTree.getSelectionRows(); |
| if (!addToSelection) { |
| clearSelection(); |
| } |
| addNext(elementsToSelect, 0, new Runnable() { |
| @Override |
| public void run() { |
| if (getTree().isSelectionEmpty()) { |
| processInnerChange(new Runnable() { |
| @Override |
| public void run() { |
| restoreSelection(currentElements); |
| } |
| }); |
| } |
| runDone(onDone); |
| } |
| }, originalRows, deferred, scrollToVisible, canSmartExpand); |
| } |
| else { |
| addToDeferred(elementsToSelect, onDone, addToSelection); |
| } |
| } |
| finally { |
| maybeReady(); |
| } |
| } |
| }); |
| } |
| |
| private void clearSelection() { |
| mySelectionIsBeingAdjusted = true; |
| try { |
| myTree.clearSelection(); |
| } |
| finally { |
| mySelectionIsBeingAdjusted = false; |
| } |
| } |
| |
| public boolean isSelectionBeingAdjusted() { |
| return mySelectionIsBeingAdjusted; |
| } |
| |
| private void restoreSelection(@NotNull Set<Object> selection) { |
| for (Object each : selection) { |
| DefaultMutableTreeNode node = getNodeForElement(each, false); |
| if (node != null && isValidForSelectionAdjusting(node)) { |
| addSelectionPath(getPathFor(node), false, null, null); |
| } |
| } |
| } |
| |
| |
| private void addToDeferred(@NotNull final Object[] elementsToSelect, final Runnable onDone, final boolean addToSelection) { |
| if (!addToSelection) { |
| myDeferredSelections.clear(); |
| } |
| myDeferredSelections.add(new Runnable() { |
| @Override |
| public void run() { |
| select(elementsToSelect, onDone, addToSelection, true); |
| } |
| }); |
| } |
| |
| private boolean checkDeferred(boolean isDeferred, @Nullable Runnable onDone) { |
| if (!isDeferred || myCanProcessDeferredSelections || !wasRootNodeInitialized()) { |
| return true; |
| } |
| else { |
| runDone(onDone); |
| return false; |
| } |
| } |
| |
| @NotNull |
| final Set<Object> getSelectedElements() { |
| TreePath[] paths = myTree.getSelectionPaths(); |
| |
| Set<Object> result = ContainerUtil.newLinkedHashSet(); |
| if (paths != null) { |
| for (TreePath eachPath : paths) { |
| if (eachPath.getLastPathComponent() instanceof DefaultMutableTreeNode) { |
| DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)eachPath.getLastPathComponent(); |
| if (eachNode == myRootNode && !myTree.isRootVisible()) continue; |
| Object eachElement = getElementFor(eachNode); |
| if (eachElement != null) { |
| result.add(eachElement); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| |
| private void addNext(@NotNull final Object[] elements, |
| final int i, |
| @Nullable final Runnable onDone, |
| final int[] originalRows, |
| final boolean deferred, |
| final boolean scrollToVisible, |
| final boolean canSmartExpand) { |
| if (i >= elements.length) { |
| if (myTree.isSelectionEmpty()) { |
| myTree.setSelectionRows(originalRows); |
| } |
| runDone(onDone); |
| } |
| else { |
| if (!checkDeferred(deferred, onDone)) { |
| return; |
| } |
| |
| doSelect(elements[i], new Runnable() { |
| @Override |
| public void run() { |
| if (!checkDeferred(deferred, onDone)) return; |
| |
| addNext(elements, i + 1, onDone, originalRows, deferred, scrollToVisible, canSmartExpand); |
| } |
| }, true, deferred, i == 0, scrollToVisible, canSmartExpand); |
| } |
| } |
| |
| public void select(@Nullable Object element, @Nullable final Runnable onDone) { |
| select(element, onDone, false); |
| } |
| |
| public void select(@Nullable Object element, @Nullable final Runnable onDone, boolean addToSelection) { |
| if (element == null) return; |
| _select(new Object[]{element}, onDone, addToSelection, true, false); |
| } |
| |
| private void doSelect(@NotNull final Object element, |
| final Runnable onDone, |
| final boolean addToSelection, |
| final boolean deferred, |
| final boolean canBeCentered, |
| final boolean scrollToVisible, |
| final boolean canSmartExpand) { |
| final Runnable _onDone = new Runnable() { |
| @Override |
| public void run() { |
| if (!checkDeferred(deferred, onDone)) return; |
| |
| checkPathAndMaybeRevalidate(element, new Runnable() { |
| @Override |
| public void run() { |
| selectVisible(element, onDone, addToSelection, canBeCentered, scrollToVisible); |
| } |
| }, true, false, canSmartExpand); |
| } |
| }; |
| _expand(element, _onDone, true, false, canSmartExpand); |
| } |
| |
| private void checkPathAndMaybeRevalidate(@NotNull Object element, @NotNull final Runnable onDone, final boolean parentsOnly, final boolean checkIfInStructure, final boolean canSmartExpand) { |
| boolean toRevalidate = isValid(element) && !myRevalidatedObjects.contains(element) && getNodeForElement(element, false) == null && isInStructure(element); |
| if (!toRevalidate) { |
| runDone(onDone); |
| return; |
| } |
| |
| myRevalidatedObjects.add(element); |
| AsyncResult<Object> revalidated = getBuilder().revalidateElement(element); |
| |
| revalidated.doWhenDone(new Consumer<Object>() { |
| @Override |
| public void consume(final Object o) { |
| invokeLaterIfNeeded(false, new Runnable() { |
| @Override |
| public void run() { |
| _expand(o, onDone, parentsOnly, checkIfInStructure, canSmartExpand); |
| } |
| }); |
| } |
| }).doWhenRejected(new Runnable() { |
| @Override |
| public void run() { |
| runDone(onDone); |
| } |
| }); |
| } |
| |
| public void scrollSelectionToVisible(@Nullable final Runnable onDone, final boolean shouldBeCentered) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (isReleased()) return; |
| |
| int[] rows = myTree.getSelectionRows(); |
| if (rows == null || rows.length == 0) { |
| runDone(onDone); |
| return; |
| } |
| |
| |
| Object toSelect = null; |
| for (int eachRow : rows) { |
| TreePath path = myTree.getPathForRow(eachRow); |
| toSelect = getElementFor(path.getLastPathComponent()); |
| if (toSelect != null) break; |
| } |
| |
| if (toSelect != null) { |
| selectVisible(toSelect, onDone, true, shouldBeCentered, true); |
| } |
| } |
| }); |
| } |
| |
| private void selectVisible(@NotNull Object element, final Runnable onDone, boolean addToSelection, boolean canBeCentered, final boolean scroll) { |
| final DefaultMutableTreeNode toSelect = getNodeForElement(element, false); |
| |
| if (toSelect == null) { |
| runDone(onDone); |
| return; |
| } |
| |
| if (getRootNode() == toSelect && !myTree.isRootVisible()) { |
| runDone(onDone); |
| return; |
| } |
| |
| int preselectedRow = getRowIfUnderSelection(element); |
| |
| final int row = preselectedRow == -1 ? myTree.getRowForPath(new TreePath(toSelect.getPath())) : preselectedRow; |
| |
| if (myUpdaterState != null) { |
| myUpdaterState.addSelection(element); |
| } |
| |
| if (Registry.is("ide.tree.autoscrollToVCenter") && canBeCentered) { |
| setHoldSize(false); |
| runDone(new Runnable() { |
| @Override |
| public void run() { |
| TreeUtil.showRowCentered(myTree, row, false, scroll).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| runDone(onDone); |
| } |
| }); |
| } |
| }); |
| } |
| else { |
| setHoldSize(false); |
| TreeUtil.showAndSelect(myTree, row - 2, row + 2, row, -1, addToSelection, scroll).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| runDone(onDone); |
| } |
| }); |
| } |
| } |
| |
| private int getRowIfUnderSelection(@NotNull Object element) { |
| int preselectedRow = -1; |
| |
| final Set<Object> selection = getSelectedElements(); |
| |
| if (selection.contains(element)) { |
| final TreePath[] paths = getTree().getSelectionPaths(); |
| for (TreePath each : paths) { |
| if (element.equals(getElementFor(each.getLastPathComponent()))) { |
| preselectedRow = getTree().getRowForPath(each); |
| break; |
| } |
| } |
| } |
| else if (myElementToNodeMap.get(TreeAnchorizer.getService().createAnchor(element)) instanceof List) { |
| final TreePath[] paths = getTree().getSelectionPaths(); |
| if (paths != null && paths.length > 0) { |
| Set<DefaultMutableTreeNode> selectedNodes = new HashSet<DefaultMutableTreeNode>(); |
| for (TreePath eachPAth : paths) { |
| if (eachPAth.getLastPathComponent() instanceof DefaultMutableTreeNode) { |
| selectedNodes.add((DefaultMutableTreeNode)eachPAth.getLastPathComponent()); |
| } |
| } |
| |
| |
| final List nodes = (List)myElementToNodeMap.get(TreeAnchorizer.getService().createAnchor(element)); |
| for (Object each : nodes) { |
| DefaultMutableTreeNode eachNode = (DefaultMutableTreeNode)each; |
| while (eachNode != null) { |
| if (selectedNodes.contains(eachNode)) { |
| preselectedRow = getTree().getRowForPath(getPathFor(eachNode)); |
| break; |
| } |
| eachNode = (DefaultMutableTreeNode)eachNode.getParent(); |
| } |
| |
| if (preselectedRow >= 0) break; |
| } |
| } |
| |
| } |
| |
| |
| return preselectedRow; |
| } |
| |
| public void expandAllWithoutRecursion(@Nullable final Runnable onDone) { |
| final JTree tree = getTree(); |
| if (tree.getRowCount() > 0) { |
| int myCurrentRow = 0; |
| while (myCurrentRow < tree.getRowCount()) { |
| final TreePath path = tree.getPathForRow(myCurrentRow); |
| final Object last = path.getLastPathComponent(); |
| final Object elem = getElementFor(last); |
| expand(elem, null); |
| myCurrentRow++; |
| } |
| } |
| runDone(onDone); |
| } |
| |
| public void expandAll(@Nullable final Runnable onDone) { |
| final JTree tree = getTree(); |
| if (tree.getRowCount() > 0) { |
| final int expandRecursionDepth = Math.max(2, Registry.intValue("ide.tree.expandRecursionDepth")); |
| new Runnable() { |
| private int myCurrentRow = 0; |
| private int myInvocationCount = 0; |
| |
| @Override |
| public void run() { |
| if (++myInvocationCount > expandRecursionDepth) { |
| myInvocationCount = 0; |
| if (isPassthroughMode()) { |
| run(); |
| } |
| else { |
| // need this to prevent stack overflow if the tree is rather big and is "synchronous" |
| SwingUtilities.invokeLater(this); |
| } |
| } |
| else { |
| final int row = myCurrentRow++; |
| if (row < tree.getRowCount()) { |
| final TreePath path = tree.getPathForRow(row); |
| final Object last = path.getLastPathComponent(); |
| final Object elem = getElementFor(last); |
| expand(elem, this); |
| } |
| else { |
| runDone(onDone); |
| } |
| } |
| } |
| }.run(); |
| } |
| else { |
| runDone(onDone); |
| } |
| } |
| |
| public void expand(final Object element, @Nullable final Runnable onDone) { |
| expand(new Object[]{element}, onDone); |
| } |
| |
| public void expand(@NotNull final Object[] element, @Nullable final Runnable onDone) { |
| expand(element, onDone, false); |
| } |
| |
| |
| void expand(@NotNull final Object[] element, @Nullable final Runnable onDone, boolean checkIfInStructure) { |
| _expand(element, onDone == null ? new EmptyRunnable() : onDone, false, checkIfInStructure, false); |
| } |
| |
| void _expand(@NotNull final Object[] element, |
| @NotNull final Runnable onDone, |
| final boolean parentsOnly, |
| final boolean checkIfInStructure, |
| final boolean canSmartExpand) { |
| |
| try { |
| runDone(new Runnable() { |
| @Override |
| public void run() { |
| if (element.length == 0) { |
| runDone(onDone); |
| return; |
| } |
| |
| if (myUpdaterState != null) { |
| myUpdaterState.clearExpansion(); |
| } |
| |
| |
| final ActionCallback done = new ActionCallback(element.length); |
| done.doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| runDone(onDone); |
| } |
| }).doWhenRejected(new Runnable() { |
| @Override |
| public void run() { |
| runDone(onDone); |
| } |
| }); |
| |
| expandNext(element, 0, parentsOnly, checkIfInStructure, canSmartExpand, done, 0); |
| } |
| }); |
| } |
| catch (ProcessCanceledException e) { |
| try { |
| runDone(onDone); |
| } |
| catch (ProcessCanceledException ignored) { |
| //todo[kirillk] added by Nik to fix IDEA-58475. I'm not sure that it is correct solution |
| } |
| } |
| } |
| |
| private void expandNext(@NotNull final Object[] elements, |
| final int index, |
| final boolean parentsOnly, |
| final boolean checkIfInStricture, |
| final boolean canSmartExpand, |
| @NotNull final ActionCallback done, |
| final int currentDepth) { |
| if (elements.length <= 0) { |
| done.setDone(); |
| return; |
| } |
| |
| if (index >= elements.length) { |
| return; |
| } |
| |
| final int[] actualDepth = {currentDepth}; |
| boolean breakCallChain = false; |
| if (actualDepth[0] > Registry.intValue("ide.tree.expandRecursionDepth")) { |
| actualDepth[0] = 0; |
| breakCallChain = true; |
| } |
| |
| Runnable expandRunnable = new Runnable() { |
| @Override |
| public void run() { |
| _expand(elements[index], new Runnable() { |
| @Override |
| public void run() { |
| done.setDone(); |
| expandNext(elements, index + 1, parentsOnly, checkIfInStricture, canSmartExpand, done, actualDepth[0] + 1); |
| } |
| }, parentsOnly, checkIfInStricture, canSmartExpand); |
| } |
| }; |
| |
| if (breakCallChain && !isPassthroughMode()) { |
| SwingUtilities.invokeLater(expandRunnable); |
| } |
| else { |
| expandRunnable.run(); |
| } |
| } |
| |
| public void collapseChildren(@NotNull final Object element, @Nullable final Runnable onDone) { |
| runDone(new Runnable() { |
| @Override |
| public void run() { |
| final DefaultMutableTreeNode node = getNodeForElement(element, false); |
| if (node != null) { |
| getTree().collapsePath(new TreePath(node.getPath())); |
| runDone(onDone); |
| } |
| } |
| }); |
| } |
| |
| private void runDone(@Nullable Runnable done) { |
| if (done == null) return; |
| |
| if (!canInitiateNewActivity()) { |
| if (done instanceof AbstractTreeBuilder.UserRunnable) { |
| return; |
| } |
| } |
| |
| if (isYeildingNow()) { |
| if (!myYieldingDoneRunnables.contains(done)) { |
| myYieldingDoneRunnables.add(done); |
| } |
| } |
| else { |
| try { |
| execute(done); |
| } |
| catch (ProcessCanceledException ignored) { |
| } |
| } |
| } |
| |
| private void _expand(final Object element, |
| @NotNull final Runnable onDone, |
| final boolean parentsOnly, |
| boolean checkIfInStructure, |
| boolean canSmartExpand) { |
| |
| if (checkIfInStructure && !isInStructure(element)) { |
| runDone(onDone); |
| return; |
| } |
| |
| if (wasRootNodeInitialized()) { |
| List<Object> kidsToExpand = new ArrayList<Object>(); |
| Object eachElement = element; |
| DefaultMutableTreeNode firstVisible = null; |
| while (true) { |
| if (eachElement == null || !isValid(eachElement)) break; |
| |
| final int preselected = getRowIfUnderSelection(eachElement); |
| if (preselected >= 0) { |
| firstVisible = (DefaultMutableTreeNode)getTree().getPathForRow(preselected).getLastPathComponent(); |
| } |
| else { |
| firstVisible = getNodeForElement(eachElement, true); |
| } |
| |
| |
| if (eachElement != element || !parentsOnly) { |
| kidsToExpand.add(eachElement); |
| } |
| if (firstVisible != null) break; |
| eachElement = getTreeStructure().getParentElement(eachElement); |
| if (eachElement == null) { |
| firstVisible = null; |
| break; |
| } |
| |
| int i = kidsToExpand.indexOf(eachElement); |
| if (i != -1) { |
| try { |
| Object existing = kidsToExpand.get(i); |
| LOG.error("Tree path contains equal elements at different levels:\n" + |
| " element: '" + eachElement + "'; " + eachElement.getClass() + " ("+System.identityHashCode(eachElement)+");\n" + |
| "existing: '" + existing + "'; " + existing.getClass()+ " ("+System.identityHashCode(existing)+"); " + |
| "path='" + kidsToExpand + "'; tree structure=" + myTreeStructure); |
| } |
| catch (AssertionError ignored) { |
| } |
| runDone(onDone); |
| throw new ProcessCanceledException(); |
| } |
| } |
| |
| |
| if (firstVisible == null) { |
| runDone(onDone); |
| } |
| else if (kidsToExpand.isEmpty()) { |
| final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)firstVisible.getParent(); |
| if (parentNode != null) { |
| final TreePath parentPath = new TreePath(parentNode.getPath()); |
| if (!myTree.isExpanded(parentPath)) { |
| expand(parentPath, canSmartExpand); |
| } |
| } |
| runDone(onDone); |
| } |
| else { |
| processExpand(firstVisible, kidsToExpand, kidsToExpand.size() - 1, onDone, canSmartExpand); |
| } |
| } |
| else { |
| deferExpansion(element, onDone, parentsOnly, canSmartExpand); |
| } |
| } |
| |
| private void deferExpansion(final Object element, @NotNull final Runnable onDone, final boolean parentsOnly, final boolean canSmartExpand) { |
| myDeferredExpansions.add(new Runnable() { |
| @Override |
| public void run() { |
| _expand(element, onDone, parentsOnly, false, canSmartExpand); |
| } |
| }); |
| } |
| |
| private void processExpand(final DefaultMutableTreeNode toExpand, |
| @NotNull final List<Object> kidsToExpand, |
| final int expandIndex, |
| @NotNull final Runnable onDone, |
| final boolean canSmartExpand) { |
| |
| final Object element = getElementFor(toExpand); |
| if (element == null) { |
| runDone(onDone); |
| return; |
| } |
| |
| addNodeAction(element, new NodeAction() { |
| @Override |
| public void onReady(@NotNull final DefaultMutableTreeNode node) { |
| if (node.getChildCount() > 0 && !myTree.isExpanded(new TreePath(node.getPath()))) { |
| if (!isAutoExpand(node)) { |
| expand(node, canSmartExpand); |
| } |
| } |
| |
| if (expandIndex <= 0) { |
| runDone(onDone); |
| return; |
| } |
| |
| checkPathAndMaybeRevalidate(kidsToExpand.get(expandIndex - 1), new Runnable() { |
| @Override |
| public void run() { |
| final DefaultMutableTreeNode nextNode = getNodeForElement(kidsToExpand.get(expandIndex - 1), false); |
| processExpand(nextNode, kidsToExpand, expandIndex - 1, onDone, canSmartExpand); |
| } |
| }, false, false, canSmartExpand); |
| } |
| }, true); |
| |
| |
| boolean childrenToUpdate = areChildrenToBeUpdated(toExpand); |
| boolean expanded = myTree.isExpanded(getPathFor(toExpand)); |
| boolean unbuilt = myUnbuiltNodes.contains(toExpand); |
| |
| if (expanded) { |
| if (unbuilt && !childrenToUpdate || childrenToUpdate) { |
| addSubtreeToUpdate(toExpand); |
| } |
| } |
| else { |
| expand(toExpand, canSmartExpand); |
| } |
| |
| if (!unbuilt && !childrenToUpdate) { |
| processNodeActionsIfReady(toExpand); |
| } |
| } |
| |
| private boolean areChildrenToBeUpdated(DefaultMutableTreeNode node) { |
| return getUpdater().isEnqueuedToUpdate(node) || isUpdatingParent(node) || myCancelledBuild.containsKey(node); |
| } |
| |
| @Nullable |
| public Object getElementFor(Object node) { |
| if (!(node instanceof DefaultMutableTreeNode)) return null; |
| return getElementFor((DefaultMutableTreeNode)node); |
| } |
| |
| @Nullable |
| Object getElementFor(@Nullable DefaultMutableTreeNode node) { |
| if (node != null) { |
| final Object o = node.getUserObject(); |
| if (o instanceof NodeDescriptor) { |
| return getElementFromDescriptor((NodeDescriptor)o); |
| } |
| } |
| |
| return null; |
| } |
| |
| public final boolean isNodeBeingBuilt(@NotNull final TreePath path) { |
| return isNodeBeingBuilt(path.getLastPathComponent()); |
| } |
| |
| public final boolean isNodeBeingBuilt(@NotNull Object node) { |
| return getParentBuiltNode(node) != null || myRootNode == node && !wasRootNodeInitialized(); |
| } |
| |
| @Nullable |
| public final DefaultMutableTreeNode getParentBuiltNode(@NotNull Object node) { |
| DefaultMutableTreeNode parent = getParentLoadingInBackground(node); |
| if (parent != null) return parent; |
| |
| if (isLoadingParentInBackground(node)) return (DefaultMutableTreeNode)node; |
| |
| DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node; |
| |
| final boolean childrenAreNoLoadedYet = myUnbuiltNodes.contains(treeNode) || isUpdatingChildrenNow(treeNode); |
| if (childrenAreNoLoadedYet) { |
| final TreePath nodePath = new TreePath(treeNode.getPath()); |
| if (!myTree.isExpanded(nodePath)) return null; |
| |
| return (DefaultMutableTreeNode)node; |
| } |
| |
| |
| return null; |
| } |
| |
| private boolean isLoadingParentInBackground(Object node) { |
| return node instanceof DefaultMutableTreeNode && isLoadedInBackground(getElementFor((DefaultMutableTreeNode)node)); |
| } |
| |
| public void setTreeStructure(@NotNull AbstractTreeStructure treeStructure) { |
| myTreeStructure = treeStructure; |
| clearUpdaterState(); |
| } |
| |
| public AbstractTreeUpdater getUpdater() { |
| return myUpdater; |
| } |
| |
| public void setUpdater(@Nullable final AbstractTreeUpdater updater) { |
| myUpdater = updater; |
| if (updater != null && myUpdateIfInactive) { |
| updater.showNotify(); |
| } |
| |
| if (myUpdater != null) { |
| myUpdater.setPassThroughMode(myPassThroughMode); |
| } |
| } |
| |
| public DefaultMutableTreeNode getRootNode() { |
| return myRootNode; |
| } |
| |
| public void setRootNode(@NotNull final DefaultMutableTreeNode rootNode) { |
| myRootNode = rootNode; |
| } |
| |
| private void dropUpdaterStateIfExternalChange() { |
| if (!isInnerChange()) { |
| clearUpdaterState(); |
| myAutoExpandRoots.clear(); |
| mySelectionIsAdjusted = false; |
| } |
| } |
| |
| void clearUpdaterState() { |
| myUpdaterState = null; |
| } |
| |
| private void createMapping(@NotNull Object element, DefaultMutableTreeNode node) { |
| element = TreeAnchorizer.getService().createAnchor(element); |
| if (!myElementToNodeMap.containsKey(element)) { |
| myElementToNodeMap.put(element, node); |
| } |
| else { |
| final Object value = myElementToNodeMap.get(element); |
| final List<DefaultMutableTreeNode> nodes; |
| if (value instanceof DefaultMutableTreeNode) { |
| nodes = new ArrayList<DefaultMutableTreeNode>(); |
| nodes.add((DefaultMutableTreeNode)value); |
| myElementToNodeMap.put(element, nodes); |
| } |
| else { |
| nodes = (List<DefaultMutableTreeNode>)value; |
| } |
| nodes.add(node); |
| } |
| } |
| |
| private void removeMapping(@NotNull Object element, DefaultMutableTreeNode node, @Nullable Object elementToPutNodeActionsFor) { |
| element = TreeAnchorizer.getService().createAnchor(element); |
| final Object value = myElementToNodeMap.get(element); |
| if (value != null) { |
| if (value instanceof DefaultMutableTreeNode) { |
| if (value.equals(node)) { |
| myElementToNodeMap.remove(element); |
| } |
| } |
| else { |
| List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value; |
| final boolean reallyRemoved = nodes.remove(node); |
| if (reallyRemoved) { |
| if (nodes.isEmpty()) { |
| myElementToNodeMap.remove(element); |
| } |
| } |
| } |
| } |
| |
| remapNodeActions(element, elementToPutNodeActionsFor); |
| } |
| |
| private void remapNodeActions(Object element, Object elementToPutNodeActionsFor) { |
| _remapNodeActions(element, elementToPutNodeActionsFor, myNodeActions); |
| _remapNodeActions(element, elementToPutNodeActionsFor, myNodeChildrenActions); |
| } |
| |
| private static void _remapNodeActions(Object element, @Nullable Object elementToPutNodeActionsFor, @NotNull final Map<Object, List<NodeAction>> nodeActions) { |
| final List<NodeAction> actions = nodeActions.get(element); |
| nodeActions.remove(element); |
| |
| if (elementToPutNodeActionsFor != null && actions != null) { |
| nodeActions.put(elementToPutNodeActionsFor, actions); |
| } |
| } |
| |
| @Nullable |
| private DefaultMutableTreeNode getFirstNode(Object element) { |
| return findNode(element, 0); |
| } |
| |
| @Nullable |
| private DefaultMutableTreeNode findNode(final Object element, int startIndex) { |
| final Object value = getBuilder().findNodeByElement(element); |
| if (value == null) { |
| return null; |
| } |
| if (value instanceof DefaultMutableTreeNode) { |
| return startIndex == 0 ? (DefaultMutableTreeNode)value : null; |
| } |
| final List<DefaultMutableTreeNode> nodes = (List<DefaultMutableTreeNode>)value; |
| return startIndex < nodes.size() ? nodes.get(startIndex) : null; |
| } |
| |
| protected Object findNodeByElement(Object element) { |
| element = TreeAnchorizer.getService().createAnchor(element); |
| if (myElementToNodeMap.containsKey(element)) { |
| return myElementToNodeMap.get(element); |
| } |
| |
| try { |
| TREE_NODE_WRAPPER.setValue(element); |
| return myElementToNodeMap.get(TREE_NODE_WRAPPER); |
| } |
| finally { |
| TREE_NODE_WRAPPER.setValue(null); |
| } |
| } |
| |
| @Nullable |
| private DefaultMutableTreeNode findNodeForChildElement(@NotNull DefaultMutableTreeNode parentNode, Object element) { |
| element = TreeAnchorizer.getService().createAnchor(element); |
| final Object value = myElementToNodeMap.get(element); |
| if (value == null) { |
| return null; |
| } |
| |
| if (value instanceof DefaultMutableTreeNode) { |
| final DefaultMutableTreeNode elementNode = (DefaultMutableTreeNode)value; |
| return parentNode.equals(elementNode.getParent()) ? elementNode : null; |
| } |
| |
| final List<DefaultMutableTreeNode> allNodesForElement = (List<DefaultMutableTreeNode>)value; |
| for (final DefaultMutableTreeNode elementNode : allNodesForElement) { |
| if (parentNode.equals(elementNode.getParent())) { |
| return elementNode; |
| } |
| } |
| |
| return null; |
| } |
| |
| private void addNodeAction(Object element, NodeAction action, boolean shouldChildrenBeReady) { |
| _addNodeAction(element, action, myNodeActions); |
| if (shouldChildrenBeReady) { |
| _addNodeAction(element, action, myNodeChildrenActions); |
| } |
| } |
| |
| |
| public void addActivity() { |
| if (myActivityMonitor != null) { |
| myActivityMonitor.addActivity(myActivityId, getUpdater().getModalityState()); |
| } |
| } |
| |
| public void removeActivity() { |
| if (myActivityMonitor != null) { |
| myActivityMonitor.removeActivity(myActivityId); |
| } |
| } |
| |
| private void _addNodeAction(Object element, NodeAction action, @NotNull Map<Object, List<NodeAction>> map) { |
| maybeSetBusyAndScheduleWaiterForReady(true, element); |
| List<NodeAction> list = map.get(element); |
| if (list == null) { |
| list = new ArrayList<NodeAction>(); |
| map.put(element, list); |
| } |
| list.add(action); |
| |
| addActivity(); |
| } |
| |
| |
| private void cleanUpNow() { |
| if (!canInitiateNewActivity()) return; |
| |
| final UpdaterTreeState state = new UpdaterTreeState(this); |
| |
| myTree.collapsePath(new TreePath(myTree.getModel().getRoot())); |
| clearSelection(); |
| getRootNode().removeAllChildren(); |
| |
| myRootNodeWasQueuedToInitialize = false; |
| myRootNodeInitialized = false; |
| |
| clearNodeActions(); |
| myElementToNodeMap.clear(); |
| myDeferredSelections.clear(); |
| myDeferredExpansions.clear(); |
| myLoadedInBackground.clear(); |
| myUnbuiltNodes.clear(); |
| myUpdateFromRootRequested = true; |
| |
| if (myWorker != null) { |
| Disposer.dispose(myWorker); |
| myWorker = null; |
| } |
| |
| myTree.invalidate(); |
| |
| state.restore(null); |
| } |
| |
| @NotNull |
| public AbstractTreeUi setClearOnHideDelay(final long clearOnHideDelay) { |
| myClearOnHideDelay = clearOnHideDelay; |
| return this; |
| } |
| |
| private class MySelectionListener implements TreeSelectionListener { |
| @Override |
| public void valueChanged(@NotNull final TreeSelectionEvent e) { |
| if (mySilentSelect != null && mySilentSelect.equals(e.getNewLeadSelectionPath())) return; |
| |
| dropUpdaterStateIfExternalChange(); |
| } |
| } |
| |
| |
| private class MyExpansionListener implements TreeExpansionListener { |
| @Override |
| public void treeExpanded(@NotNull TreeExpansionEvent event) { |
| final TreePath path = event.getPath(); |
| |
| if (mySilentExpand != null && mySilentExpand.equals(path)) return; |
| |
| dropUpdaterStateIfExternalChange(); |
| |
| if (myRequestedExpand != null && !myRequestedExpand.equals(path)) { |
| _getReady().doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| Object element = getElementFor(path.getLastPathComponent()); |
| expand(element, null); |
| } |
| }); |
| return; |
| } |
| |
| |
| final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); |
| |
| if (!myUnbuiltNodes.contains(node)) { |
| removeLoading(node, false); |
| |
| Set<DefaultMutableTreeNode> childrenToUpdate = new HashSet<DefaultMutableTreeNode>(); |
| for (int i = 0; i < node.getChildCount(); i++) { |
| DefaultMutableTreeNode each = (DefaultMutableTreeNode)node.getChildAt(i); |
| if (myUnbuiltNodes.contains(each)) { |
| makeLoadingOrLeafIfNoChildren(each); |
| childrenToUpdate.add(each); |
| } |
| } |
| |
| if (!childrenToUpdate.isEmpty()) { |
| for (DefaultMutableTreeNode each : childrenToUpdate) { |
| maybeUpdateSubtreeToUpdate(each); |
| } |
| } |
| } |
| else { |
| getBuilder().expandNodeChildren(node); |
| } |
| |
| processSmartExpand(node, canSmartExpand(node, true), false); |
| processNodeActionsIfReady(node); |
| } |
| |
| @Override |
| public void treeCollapsed(@NotNull TreeExpansionEvent e) { |
| dropUpdaterStateIfExternalChange(); |
| |
| final TreePath path = e.getPath(); |
| final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); |
| if (!(node.getUserObject() instanceof NodeDescriptor)) return; |
| |
| |
| TreePath pathToSelect = null; |
| if (isSelectionInside(node)) { |
| pathToSelect = new TreePath(node.getPath()); |
| } |
| |
| |
| NodeDescriptor descriptor = getDescriptorFrom(node); |
| if (getBuilder().isDisposeOnCollapsing(descriptor)) { |
| runDone(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed(node)) return; |
| |
| TreePath nodePath = new TreePath(node.getPath()); |
| if (myTree.isExpanded(nodePath)) return; |
| |
| removeChildren(node); |
| makeLoadingOrLeafIfNoChildren(node); |
| } |
| }); |
| if (node.equals(getRootNode())) { |
| if (myTree.isRootVisible()) { |
| //todo kirillk to investigate -- should be done by standard selction move |
| //addSelectionPath(new TreePath(getRootNode().getPath()), true, Condition.FALSE); |
| } |
| } |
| else { |
| myTreeModel.reload(node); |
| } |
| } |
| |
| if (pathToSelect != null && myTree.isSelectionEmpty()) { |
| addSelectionPath(pathToSelect, true, Conditions.alwaysFalse(), null); |
| } |
| } |
| } |
| |
| private void removeChildren(@NotNull DefaultMutableTreeNode node) { |
| EnumerationCopy copy = new EnumerationCopy(node.children()); |
| while (copy.hasMoreElements()) { |
| disposeNode((DefaultMutableTreeNode)copy.nextElement()); |
| } |
| node.removeAllChildren(); |
| myTreeModel.nodeStructureChanged(node); |
| } |
| |
| private void maybeUpdateSubtreeToUpdate(@NotNull final DefaultMutableTreeNode subtreeRoot) { |
| if (!myUnbuiltNodes.contains(subtreeRoot)) return; |
| TreePath path = getPathFor(subtreeRoot); |
| |
| if (myTree.getRowForPath(path) == -1) return; |
| |
| DefaultMutableTreeNode parent = getParentBuiltNode(subtreeRoot); |
| if (parent == null) { |
| if (!getBuilder().isAlwaysShowPlus(getDescriptorFrom(subtreeRoot))) { |
| addSubtreeToUpdate(subtreeRoot); |
| } |
| } |
| else if (parent != subtreeRoot) { |
| addNodeAction(getElementFor(subtreeRoot), new NodeAction() { |
| @Override |
| public void onReady(DefaultMutableTreeNode parent) { |
| maybeUpdateSubtreeToUpdate(subtreeRoot); |
| } |
| }, true); |
| } |
| } |
| |
| private boolean isSelectionInside(DefaultMutableTreeNode parent) { |
| TreePath path = new TreePath(myTreeModel.getPathToRoot(parent)); |
| TreePath[] paths = myTree.getSelectionPaths(); |
| if (paths == null) return false; |
| for (TreePath path1 : paths) { |
| if (path.isDescendant(path1)) return true; |
| } |
| return false; |
| } |
| |
| public boolean isInStructure(@Nullable Object element) { |
| final AbstractTreeStructure structure = getTreeStructure(); |
| if (structure == null) return false; |
| |
| final Object rootElement = structure.getRootElement(); |
| Object eachParent = element; |
| while (eachParent != null) { |
| if (Comparing.equal(rootElement, eachParent)) return true; |
| eachParent = structure.getParentElement(eachParent); |
| } |
| |
| return false; |
| } |
| |
| interface NodeAction { |
| void onReady(DefaultMutableTreeNode node); |
| } |
| |
| public void setCanYield(final boolean canYield) { |
| myCanYield = canYield; |
| } |
| |
| @NotNull |
| public Collection<TreeUpdatePass> getYeildingPasses() { |
| return myYieldingPasses; |
| } |
| |
| private static class LoadedChildren { |
| @NotNull private final List<Object> myElements; |
| private final Map<Object, NodeDescriptor> myDescriptors = new HashMap<Object, NodeDescriptor>(); |
| private final Map<NodeDescriptor, Boolean> myChanges = new HashMap<NodeDescriptor, Boolean>(); |
| |
| LoadedChildren(@Nullable Object[] elements) { |
| myElements = Arrays.asList(elements != null ? elements : ArrayUtil.EMPTY_OBJECT_ARRAY); |
| } |
| |
| void putDescriptor(Object element, NodeDescriptor descriptor, boolean isChanged) { |
| if (isUnitTestingMode()) { |
| assert myElements.contains(element); |
| } |
| myDescriptors.put(element, descriptor); |
| myChanges.put(descriptor, isChanged); |
| } |
| |
| @NotNull |
| List<Object> getElements() { |
| return myElements; |
| } |
| |
| NodeDescriptor getDescriptor(Object element) { |
| return myDescriptors.get(element); |
| } |
| |
| @NotNull |
| @Override |
| public String toString() { |
| return myElements + "->" + myChanges; |
| } |
| |
| public boolean isUpdated(Object element) { |
| NodeDescriptor desc = getDescriptor(element); |
| return myChanges.get(desc); |
| } |
| } |
| |
| private long getComparatorStamp() { |
| if (myNodeDescriptorComparator instanceof NodeDescriptor.NodeComparator) { |
| long currentComparatorStamp = ((NodeDescriptor.NodeComparator)myNodeDescriptorComparator).getStamp(); |
| if (currentComparatorStamp > myLastComparatorStamp) { |
| myOwnComparatorStamp = Math.max(myOwnComparatorStamp, currentComparatorStamp) + 1; |
| } |
| myLastComparatorStamp = currentComparatorStamp; |
| |
| return Math.max(currentComparatorStamp, myOwnComparatorStamp); |
| } |
| else { |
| return myOwnComparatorStamp; |
| } |
| } |
| |
| public void incComparatorStamp() { |
| myOwnComparatorStamp = getComparatorStamp() + 1; |
| } |
| |
| public static class UpdateInfo { |
| NodeDescriptor myDescriptor; |
| TreeUpdatePass myPass; |
| boolean myCanSmartExpand; |
| boolean myWasExpanded; |
| boolean myForceUpdate; |
| boolean myDescriptorIsUpToDate; |
| boolean myUpdateChildren; |
| |
| public UpdateInfo(NodeDescriptor descriptor, |
| TreeUpdatePass pass, |
| boolean canSmartExpand, |
| boolean wasExpanded, |
| boolean forceUpdate, |
| boolean descriptorIsUpToDate, |
| boolean updateChildren) { |
| myDescriptor = descriptor; |
| myPass = pass; |
| myCanSmartExpand = canSmartExpand; |
| myWasExpanded = wasExpanded; |
| myForceUpdate = forceUpdate; |
| myDescriptorIsUpToDate = descriptorIsUpToDate; |
| myUpdateChildren = updateChildren; |
| } |
| |
| synchronized NodeDescriptor getDescriptor() { |
| return myDescriptor; |
| } |
| |
| synchronized TreeUpdatePass getPass() { |
| return myPass; |
| } |
| |
| synchronized boolean isCanSmartExpand() { |
| return myCanSmartExpand; |
| } |
| |
| synchronized boolean isWasExpanded() { |
| return myWasExpanded; |
| } |
| |
| synchronized boolean isForceUpdate() { |
| return myForceUpdate; |
| } |
| |
| synchronized boolean isDescriptorIsUpToDate() { |
| return myDescriptorIsUpToDate; |
| } |
| |
| public synchronized void apply(@NotNull UpdateInfo updateInfo) { |
| myDescriptor = updateInfo.myDescriptor; |
| myPass = updateInfo.myPass; |
| myCanSmartExpand = updateInfo.myCanSmartExpand; |
| myWasExpanded = updateInfo.myWasExpanded; |
| myForceUpdate = updateInfo.myForceUpdate; |
| myDescriptorIsUpToDate = updateInfo.myDescriptorIsUpToDate; |
| } |
| |
| public synchronized boolean isUpdateChildren() { |
| return myUpdateChildren; |
| } |
| |
| @Override |
| @NotNull |
| @NonNls |
| public synchronized String toString() { |
| return "UpdateInfo: desc=" + |
| myDescriptor + |
| " pass=" + |
| myPass + |
| " canSmartExpand=" + |
| myCanSmartExpand + |
| " wasExpanded=" + |
| myWasExpanded + |
| " forceUpdate=" + |
| myForceUpdate + |
| " descriptorUpToDate=" + |
| myDescriptorIsUpToDate; |
| } |
| } |
| |
| |
| public void setPassthroughMode(boolean passthrough) { |
| myPassThroughMode = passthrough; |
| AbstractTreeUpdater updater = getUpdater(); |
| |
| if (updater != null) { |
| updater.setPassThroughMode(myPassThroughMode); |
| } |
| |
| if (!isUnitTestingMode() && passthrough) { |
| // TODO: this assertion should be restored back as soon as possible [JamTreeTableView should be rewritten, etc] |
| //LOG.error("Pass-through mode for TreeUi is allowed only for unit test mode"); |
| } |
| } |
| |
| public boolean isPassthroughMode() { |
| return myPassThroughMode; |
| } |
| |
| private static boolean isUnitTestingMode() { |
| Application app = ApplicationManager.getApplication(); |
| return app != null && app.isUnitTestMode(); |
| } |
| |
| private void addModelListenerToDianoseAccessOutsideEdt() { |
| myTreeModel.addTreeModelListener(new TreeModelListener() { |
| @Override |
| public void treeNodesChanged(TreeModelEvent e) { |
| assertIsDispatchThread(); |
| } |
| |
| @Override |
| public void treeNodesInserted(TreeModelEvent e) { |
| assertIsDispatchThread(); |
| } |
| |
| @Override |
| public void treeNodesRemoved(TreeModelEvent e) { |
| assertIsDispatchThread(); |
| } |
| |
| @Override |
| public void treeStructureChanged(TreeModelEvent e) { |
| assertIsDispatchThread(); |
| } |
| }); |
| } |
| |
| } |