| /* |
| * 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.xdebugger.impl.ui.tree.nodes; |
| |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.ui.SimpleTextAttributes; |
| import com.intellij.util.ObjectUtils; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.SortedList; |
| import com.intellij.xdebugger.frame.*; |
| import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants; |
| import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree; |
| import com.intellij.xdebugger.settings.XDebuggerSettingsManager; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.tree.TreeNode; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * @author nik |
| */ |
| public abstract class XValueContainerNode<ValueContainer extends XValueContainer> extends XDebuggerTreeNode implements XCompositeNode, TreeNode { |
| private List<XValueNodeImpl> myValueChildren; |
| private List<MessageTreeNode> myMessageChildren; |
| private List<MessageTreeNode> myTemporaryMessageChildren; |
| private List<XValueGroupNodeImpl> myTopGroups; |
| private List<XValueGroupNodeImpl> myBottomGroups; |
| private List<TreeNode> myCachedAllChildren; |
| protected final ValueContainer myValueContainer; |
| private volatile boolean myObsolete; |
| private volatile boolean myAlreadySorted; |
| |
| protected XValueContainerNode(XDebuggerTree tree, final XDebuggerTreeNode parent, @NotNull ValueContainer valueContainer) { |
| super(tree, parent, true); |
| myValueContainer = valueContainer; |
| } |
| |
| private void loadChildren() { |
| if (myValueChildren != null || myMessageChildren != null || myTemporaryMessageChildren != null) return; |
| startComputingChildren(); |
| } |
| |
| public void startComputingChildren() { |
| myCachedAllChildren = null; |
| setTemporaryMessageNode(createLoadingMessageNode()); |
| myValueContainer.computeChildren(this); |
| } |
| |
| protected MessageTreeNode createLoadingMessageNode() { |
| return MessageTreeNode.createLoadingMessage(myTree, this); |
| } |
| |
| @Override |
| public void setAlreadySorted(boolean alreadySorted) { |
| myAlreadySorted = alreadySorted; |
| } |
| |
| @Override |
| public void addChildren(@NotNull final XValueChildrenList children, final boolean last) { |
| invokeNodeUpdate(new Runnable() { |
| @Override |
| public void run() { |
| if (myValueChildren == null) { |
| if (!myAlreadySorted && XDebuggerSettingsManager.getInstance().getDataViewSettings().isSortValues()) { |
| myValueChildren = new SortedList<XValueNodeImpl>(XValueNodeImpl.COMPARATOR); |
| } |
| else { |
| myValueChildren = new ArrayList<XValueNodeImpl>(); |
| } |
| } |
| List<XValueContainerNode<?>> newChildren = new ArrayList<XValueContainerNode<?>>(children.size()); |
| for (int i = 0; i < children.size(); i++) { |
| XValueNodeImpl node = new XValueNodeImpl(myTree, XValueContainerNode.this, children.getName(i), children.getValue(i)); |
| myValueChildren.add(node); |
| newChildren.add(node); |
| |
| if (Registry.is("ide.debugger.inline") && "this".equals(node.getName())) { //todo[kb]: try to generify this dirty hack |
| //initialize "this" fields to display in inline view |
| node.getChildren(); |
| } |
| } |
| myTopGroups = createGroupNodes(children.getTopGroups(), myTopGroups, newChildren); |
| myBottomGroups = createGroupNodes(children.getBottomGroups(), myBottomGroups, newChildren); |
| myCachedAllChildren = null; |
| fireNodesInserted(newChildren); |
| if (last) { |
| final int[] ints = getNodesIndices(myTemporaryMessageChildren); |
| final TreeNode[] removed = getChildNodes(ints); |
| myCachedAllChildren = null; |
| myTemporaryMessageChildren = null; |
| fireNodesRemoved(ints, removed); |
| } |
| myTree.childrenLoaded(XValueContainerNode.this, newChildren, last); |
| } |
| }); |
| } |
| |
| @Nullable |
| private List<XValueGroupNodeImpl> createGroupNodes(List<XValueGroup> groups, |
| @Nullable List<XValueGroupNodeImpl> prevNodes, |
| List<XValueContainerNode<?>> newChildren) { |
| if (groups.isEmpty()) return prevNodes; |
| |
| List<XValueGroupNodeImpl> nodes = prevNodes != null ? prevNodes : new SmartList<XValueGroupNodeImpl>(); |
| for (XValueGroup group : groups) { |
| XValueGroupNodeImpl node = new XValueGroupNodeImpl(myTree, this, group); |
| nodes.add(node); |
| newChildren.add(node); |
| } |
| return nodes; |
| } |
| |
| @Override |
| public void tooManyChildren(final int remaining) { |
| invokeNodeUpdate(new Runnable() { |
| @Override |
| public void run() { |
| setTemporaryMessageNode(MessageTreeNode.createEllipsisNode(myTree, XValueContainerNode.this, remaining)); |
| } |
| }); |
| } |
| |
| @Override |
| public boolean isObsolete() { |
| return myObsolete; |
| } |
| |
| @Override |
| public void clearChildren() { |
| myCachedAllChildren = null; |
| myMessageChildren = null; |
| myTemporaryMessageChildren = null; |
| myValueChildren = null; |
| myTopGroups = null; |
| myBottomGroups = null; |
| fireNodeStructureChanged(); |
| } |
| |
| @Override |
| public void setErrorMessage(final @NotNull String errorMessage) { |
| setErrorMessage(errorMessage, null); |
| } |
| |
| @Override |
| public void setErrorMessage(@NotNull final String errorMessage, @Nullable final XDebuggerTreeNodeHyperlink link) { |
| setMessage(errorMessage, XDebuggerUIConstants.ERROR_MESSAGE_ICON, XDebuggerUIConstants.ERROR_MESSAGE_ATTRIBUTES, link); |
| } |
| |
| @Override |
| public void setMessage(@NotNull final String message, |
| final Icon icon, @NotNull final SimpleTextAttributes attributes, @Nullable final XDebuggerTreeNodeHyperlink link) { |
| invokeNodeUpdate(new Runnable() { |
| @Override |
| public void run() { |
| setMessageNodes(MessageTreeNode.createMessages(myTree, XValueContainerNode.this, message, link, |
| icon, |
| attributes), false); |
| } |
| }); |
| } |
| |
| private void setTemporaryMessageNode(final MessageTreeNode messageNode) { |
| setMessageNodes(Collections.singletonList(messageNode), true); |
| } |
| |
| private void setMessageNodes(final List<MessageTreeNode> messages, boolean temporary) { |
| myCachedAllChildren = null; |
| List<MessageTreeNode> allMessageChildren = ContainerUtil.concat(myMessageChildren != null ? myMessageChildren : Collections.<MessageTreeNode>emptyList(), |
| myTemporaryMessageChildren != null ? myTemporaryMessageChildren : Collections.<MessageTreeNode>emptyList()); |
| final int[] indices = getNodesIndices(allMessageChildren); |
| final TreeNode[] nodes = getChildNodes(indices); |
| fireNodesRemoved(indices, nodes); |
| if (!temporary) { |
| myMessageChildren = messages; |
| myTemporaryMessageChildren = null; |
| } |
| else { |
| myTemporaryMessageChildren = messages; |
| myMessageChildren = null; |
| } |
| myCachedAllChildren = null; |
| fireNodesInserted(messages); |
| } |
| |
| @NotNull |
| @Override |
| public List<? extends TreeNode> getChildren() { |
| loadChildren(); |
| |
| if (myCachedAllChildren == null) { |
| myCachedAllChildren = new ArrayList<TreeNode>(); |
| if (myMessageChildren != null) { |
| myCachedAllChildren.addAll(myMessageChildren); |
| } |
| if (myTopGroups != null) { |
| myCachedAllChildren.addAll(myTopGroups); |
| } |
| if (myValueChildren != null) { |
| myCachedAllChildren.addAll(myValueChildren); |
| } |
| if (myBottomGroups != null) { |
| myCachedAllChildren.addAll(myBottomGroups); |
| } |
| if (myTemporaryMessageChildren != null) { |
| myCachedAllChildren.addAll(myTemporaryMessageChildren); |
| } |
| } |
| return myCachedAllChildren; |
| } |
| |
| @NotNull |
| public ValueContainer getValueContainer() { |
| return myValueContainer; |
| } |
| |
| @Override |
| @Nullable |
| public List<? extends XValueContainerNode<?>> getLoadedChildren() { |
| List<? extends XValueContainerNode<?>> empty = Collections.<XValueGroupNodeImpl>emptyList(); |
| return ContainerUtil.concat(ObjectUtils.notNull(myTopGroups, empty), |
| ObjectUtils.notNull(myValueChildren, empty), |
| ObjectUtils.notNull(myBottomGroups, empty)); |
| } |
| |
| public void setObsolete() { |
| myObsolete = true; |
| } |
| } |