Java tutorial
/* * Copyright (C) 2015 The Android Open Source Project * * 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.android.tools.idea.editors.hprof.views; import com.android.tools.adtui.common.ColumnTreeBuilder; import com.android.tools.idea.actions.EditMultipleSourcesAction; import com.android.tools.idea.actions.PsiClassNavigation; import com.android.tools.idea.editors.hprof.descriptors.*; import com.android.tools.perflib.heap.*; import com.android.tools.perflib.heap.memoryanalyzer.HprofBitmapProvider; import com.intellij.debugger.engine.DebugProcessEvents; import com.intellij.debugger.engine.DebugProcessImpl; import com.intellij.debugger.engine.SuspendContextImpl; import com.intellij.debugger.engine.SuspendManagerImpl; import com.intellij.debugger.engine.events.DebuggerCommandImpl; import com.intellij.debugger.impl.DebuggerContextImpl; import com.intellij.debugger.ui.impl.DebuggerTreeRenderer; import com.intellij.debugger.ui.impl.tree.TreeBuilder; import com.intellij.debugger.ui.impl.tree.TreeBuilderNode; import com.intellij.debugger.ui.impl.watch.DebuggerTree; import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl; import com.intellij.debugger.ui.impl.watch.DefaultNodeDescriptor; import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl; import com.intellij.debugger.ui.tree.NodeDescriptor; import com.intellij.ide.DataManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.PopupHandler; import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.components.JBList; import com.sun.jdi.request.EventRequest; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import java.awt.*; import java.util.*; import java.util.List; public final class InstancesTreeView implements DataProvider, Disposable { public static final String TREE_NAME = "HprofInstancesTree"; public static final DataKey<ClassInstance> SELECTED_CLASS_INSTANCE = DataKey .create("HprofInstanceTreeView.SelectedClassInstance"); private static final int NODES_PER_EXPANSION = 100; @NotNull private Project myProject; @NotNull private DebuggerTree myDebuggerTree; @NotNull private JComponent myColumnTree; @NotNull private SelectionModel mySelectionModel; @NotNull private DebugProcessImpl myDebugProcess; @SuppressWarnings("NullableProblems") @NotNull private volatile SuspendContextImpl myDummySuspendContext; @NotNull private Heap myHeap; @Nullable private ClassObj myClassObj; @Nullable private Comparator<DebuggerTreeNodeImpl> myComparator; @NotNull private SortOrder mySortOrder = SortOrder.UNSORTED; @NotNull private GoToInstanceAction myGoToInstanceAction; public InstancesTreeView(@NotNull Project project, @NotNull SelectionModel selectionModel) { myProject = project; mySelectionModel = selectionModel; myDebuggerTree = new DebuggerTree(project) { @Override protected void build(DebuggerContextImpl context) { DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl) getModel().getRoot(); Instance instance = ((InstanceFieldDescriptorImpl) root.getDescriptor()).getInstance(); addChildren(root, null, instance); } @Override public Object getData(@NonNls String dataId) { return InstancesTreeView.this.getData(dataId); } }; myDebuggerTree.getComponent().setName(TREE_NAME); Disposer.register(myProject, this); Disposer.register(this, myDebuggerTree); myHeap = mySelectionModel.getHeap(); myDebugProcess = new DebugProcessEvents(project); final SuspendManagerImpl suspendManager = new SuspendManagerImpl(myDebugProcess); myDebugProcess.getManagerThread().invokeAndWait(new DebuggerCommandImpl() { @Override protected void action() throws Exception { myDummySuspendContext = suspendManager.pushSuspendContext(EventRequest.SUSPEND_NONE, 1); } }); final TreeBuilder model = new TreeBuilder(myDebuggerTree) { @Override public void buildChildren(TreeBuilderNode node) { final DebuggerTreeNodeImpl debuggerTreeNode = (DebuggerTreeNodeImpl) node; NodeDescriptor descriptor = debuggerTreeNode.getDescriptor(); if (descriptor instanceof DefaultNodeDescriptor) { return; } else if (descriptor instanceof ContainerDescriptorImpl) { addContainerChildren(debuggerTreeNode, 0, true); } else { InstanceFieldDescriptorImpl instanceDescriptor = (InstanceFieldDescriptorImpl) descriptor; addChildren(debuggerTreeNode, instanceDescriptor.getHprofField(), instanceDescriptor.getInstance()); } sortTree(debuggerTreeNode); myDebuggerTree.treeDidChange(); } @Override public boolean isExpandable(TreeBuilderNode builderNode) { return ((DebuggerTreeNodeImpl) builderNode).getDescriptor().isExpandable(); } }; model.setRoot(myDebuggerTree.getNodeFactory().getDefaultNode()); model.addTreeModelListener(new TreeModelListener() { @Override public void treeNodesChanged(TreeModelEvent event) { myDebuggerTree.hideTooltip(); } @Override public void treeNodesInserted(TreeModelEvent event) { myDebuggerTree.hideTooltip(); } @Override public void treeNodesRemoved(TreeModelEvent event) { myDebuggerTree.hideTooltip(); } @Override public void treeStructureChanged(TreeModelEvent event) { myDebuggerTree.hideTooltip(); } }); myDebuggerTree.setModel(model); myDebuggerTree.setRootVisible(false); myDebuggerTree.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, this); JBList contextActionList = new JBList(new EditMultipleSourcesAction()); JBPopupFactory.getInstance().createListPopupBuilder(contextActionList); myGoToInstanceAction = new GoToInstanceAction(myDebuggerTree); myDebuggerTree.addMouseListener(new PopupHandler() { @Override public void invokePopup(Component comp, int x, int y) { DefaultActionGroup popupGroup = new DefaultActionGroup(new EditMultipleSourcesAction()); Instance selectedInstance = mySelectionModel.getInstance(); if (selectedInstance instanceof ClassInstance && HprofBitmapProvider.canGetBitmapFromInstance(selectedInstance)) { popupGroup.add(new ViewBitmapAction()); } popupGroup.add(myGoToInstanceAction); ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, popupGroup).getComponent() .show(comp, x, y); } }); mySelectionModel.addListener(new SelectionModel.SelectionListener() { @Override public void onHeapChanged(@NotNull Heap heap) { if (heap != myHeap) { myHeap = heap; if (myDebuggerTree.getMutableModel().getRoot() != null) { onSelectionChanged(); } } } @Override public void onClassObjChanged(@Nullable ClassObj classObj) { if (classObj != myClassObj) { myClassObj = classObj; onSelectionChanged(); } } @Override public void onInstanceChanged(@Nullable Instance instance) { if (instance == null) { return; } TreePath path = myDebuggerTree.getSelectionPath(); if (path != null) { DebuggerTreeNodeImpl treeNode = (DebuggerTreeNodeImpl) path.getPathComponent(1); if (treeNode.getDescriptor() instanceof InstanceFieldDescriptorImpl && ((InstanceFieldDescriptorImpl) treeNode.getDescriptor()).getInstance() == instance) { return; } } DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl) myDebuggerTree.getMutableModel().getRoot(); DebuggerTreeNodeImpl targetNode = expandMoreNodesUntilFound(root, instance); if (targetNode != null) { final TreePath targetPath = new TreePath(targetNode.getPath()); myDebuggerTree.treeChanged(); myDebuggerTree.setSelectionPath(targetPath); ApplicationManager.getApplication() .invokeLater(() -> myDebuggerTree.scrollPathToVisible(targetPath)); } } private void onSelectionChanged() { DebuggerTreeNodeImpl newRoot; Instance singleChild = null; if (myClassObj != null) { ContainerDescriptorImpl containerDescriptor = new ContainerDescriptorImpl(myClassObj, myHeap.getId()); newRoot = DebuggerTreeNodeImpl.createNodeNoUpdate(myDebuggerTree, containerDescriptor); if (containerDescriptor.getInstances().size() == 1) { singleChild = containerDescriptor.getInstances().get(0); } } else { newRoot = myDebuggerTree.getNodeFactory().getDefaultNode(); } myDebuggerTree.getMutableModel().setRoot(newRoot); myDebuggerTree.treeChanged(); if (myDebuggerTree.getRowCount() > 0) { myDebuggerTree.scrollRowToVisible(0); } if (singleChild != null) { myDebuggerTree.setSelectionInterval(0, 0); mySelectionModel.setInstance(singleChild); } } }); myDebuggerTree.addTreeSelectionListener(e -> { TreePath path = e.getPath(); if (path == null || path.getPathCount() < 2 || !e.isAddedPath()) { mySelectionModel.setInstance(null); return; } DebuggerTreeNodeImpl instanceNode = (DebuggerTreeNodeImpl) path.getPathComponent(1); if (instanceNode.getDescriptor() instanceof InstanceFieldDescriptorImpl) { InstanceFieldDescriptorImpl descriptor = (InstanceFieldDescriptorImpl) instanceNode.getDescriptor(); if (descriptor.getInstance() != mySelectionModel.getInstance()) { mySelectionModel.setInstance(descriptor.getInstance()); } } // Handle node expansions (this is present when the list is large). DebuggerTreeNodeImpl lastPathNode = (DebuggerTreeNodeImpl) path.getLastPathComponent(); if (lastPathNode.getDescriptor() instanceof ExpansionDescriptorImpl) { ExpansionDescriptorImpl expansionDescriptor = (ExpansionDescriptorImpl) lastPathNode .getDescriptor(); DebuggerTreeNodeImpl parentNode = lastPathNode.getParent(); myDebuggerTree.getMutableModel().removeNodeFromParent(lastPathNode); if (parentNode.getDescriptor() instanceof ContainerDescriptorImpl) { addContainerChildren(parentNode, expansionDescriptor.getStartIndex(), true); } else if (parentNode.getDescriptor() instanceof InstanceFieldDescriptorImpl) { InstanceFieldDescriptorImpl instanceFieldDescriptor = (InstanceFieldDescriptorImpl) parentNode .getDescriptor(); addChildren(parentNode, instanceFieldDescriptor.getHprofField(), instanceFieldDescriptor.getInstance(), expansionDescriptor.getStartIndex()); } sortTree(parentNode); myDebuggerTree.getMutableModel().nodeStructureChanged(parentNode); if (myComparator != null) { myDebuggerTree.scrollPathToVisible( new TreePath(((DebuggerTreeNodeImpl) parentNode.getLastChild()).getPath())); } } }); ColumnTreeBuilder builder = new ColumnTreeBuilder(myDebuggerTree) .addColumn(new ColumnTreeBuilder.ColumnBuilder().setName("Instance").setPreferredWidth(600) .setHeaderAlignment(SwingConstants.LEFT) .setComparator(new Comparator<DebuggerTreeNodeImpl>() { @Override public int compare(@NotNull DebuggerTreeNodeImpl a, @NotNull DebuggerTreeNodeImpl b) { return getDefaultOrdering(a, b); } }).setRenderer((DebuggerTreeRenderer) myDebuggerTree.getCellRenderer())) .addColumn(new ColumnTreeBuilder.ColumnBuilder().setName("Depth").setPreferredWidth(60) .setHeaderAlignment(SwingConstants.RIGHT) .setComparator(new Comparator<DebuggerTreeNodeImpl>() { @Override public int compare(DebuggerTreeNodeImpl a, DebuggerTreeNodeImpl b) { int depthA = 0; int depthB = 0; if (a.getDescriptor() instanceof InstanceFieldDescriptorImpl) { Instance instanceA = (Instance) ((InstanceFieldDescriptorImpl) a .getDescriptor()).getValueData(); if (instanceA != null) { depthA = instanceA.getDistanceToGcRoot(); } } if (b.getDescriptor() instanceof InstanceFieldDescriptorImpl) { Instance instanceB = (Instance) ((InstanceFieldDescriptorImpl) b .getDescriptor()).getValueData(); if (instanceB != null) { depthB = instanceB.getDistanceToGcRoot(); } } if (depthA != depthB) { return depthA - depthB; } else { return getDefaultOrdering(a, b); } } }).setRenderer(new ColoredTreeCellRenderer() { @Override public void customizeCellRenderer(@NotNull JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { NodeDescriptorImpl nodeDescriptor = (NodeDescriptorImpl) ((TreeBuilderNode) value) .getUserObject(); if (nodeDescriptor instanceof InstanceFieldDescriptorImpl) { InstanceFieldDescriptorImpl descriptor = (InstanceFieldDescriptorImpl) nodeDescriptor; assert !descriptor.isPrimitive(); Instance instance = (Instance) descriptor.getValueData(); if (instance != null && instance.getDistanceToGcRoot() != Integer.MAX_VALUE) { append(String.valueOf(instance.getDistanceToGcRoot()), SimpleTextAttributes.REGULAR_ATTRIBUTES); } } setTextAlign(SwingConstants.RIGHT); } })) .addColumn(new ColumnTreeBuilder.ColumnBuilder().setName("Shallow Size").setPreferredWidth(80) .setHeaderAlignment(SwingConstants.RIGHT) .setComparator(new Comparator<DebuggerTreeNodeImpl>() { @Override public int compare(@NotNull DebuggerTreeNodeImpl a, @NotNull DebuggerTreeNodeImpl b) { int sizeA = 0; int sizeB = 0; if (a.getDescriptor() instanceof InstanceFieldDescriptorImpl) { Instance instanceA = (Instance) ((InstanceFieldDescriptorImpl) a .getDescriptor()).getValueData(); if (instanceA != null) { sizeA = instanceA.getSize(); } } if (b.getDescriptor() instanceof InstanceFieldDescriptorImpl) { Instance instanceB = (Instance) ((InstanceFieldDescriptorImpl) b .getDescriptor()).getValueData(); if (instanceB != null) { sizeB = instanceB.getSize(); } } if (sizeA != sizeB) { return sizeA - sizeB; } else { return getDefaultOrdering(a, b); } } }).setRenderer(new ColoredTreeCellRenderer() { @Override public void customizeCellRenderer(@NotNull JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { NodeDescriptorImpl nodeDescriptor = (NodeDescriptorImpl) ((TreeBuilderNode) value) .getUserObject(); if (nodeDescriptor instanceof InstanceFieldDescriptorImpl) { InstanceFieldDescriptorImpl descriptor = (InstanceFieldDescriptorImpl) nodeDescriptor; assert !descriptor.isPrimitive(); Instance instance = (Instance) descriptor.getValueData(); if (instance != null) { append(String.valueOf(instance.getSize()), SimpleTextAttributes.REGULAR_ATTRIBUTES); } } setTextAlign(SwingConstants.RIGHT); } })) .addColumn(new ColumnTreeBuilder.ColumnBuilder().setName("Dominating Size").setPreferredWidth(80) .setHeaderAlignment(SwingConstants.RIGHT) .setComparator(new Comparator<DebuggerTreeNodeImpl>() { @Override public int compare(@NotNull DebuggerTreeNodeImpl a, @NotNull DebuggerTreeNodeImpl b) { long sizeA = 0; long sizeB = 0; if (a.getDescriptor() instanceof InstanceFieldDescriptorImpl) { Instance instanceA = (Instance) ((InstanceFieldDescriptorImpl) a .getDescriptor()).getValueData(); if (instanceA != null && instanceA.getDistanceToGcRoot() != Integer.MAX_VALUE) { sizeA = instanceA.getTotalRetainedSize(); } } if (b.getDescriptor() instanceof InstanceFieldDescriptorImpl) { Instance instanceB = (Instance) ((InstanceFieldDescriptorImpl) b .getDescriptor()).getValueData(); if (instanceB != null && instanceB.getDistanceToGcRoot() != Integer.MAX_VALUE) { sizeB = instanceB.getTotalRetainedSize(); } } if (sizeA != sizeB) { return (int) (sizeA - sizeB); } else { return getDefaultOrdering(a, b); } } }).setRenderer(new ColoredTreeCellRenderer() { @Override public void customizeCellRenderer(@NotNull JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { NodeDescriptorImpl nodeDescriptor = (NodeDescriptorImpl) ((TreeBuilderNode) value) .getUserObject(); if (nodeDescriptor instanceof InstanceFieldDescriptorImpl) { InstanceFieldDescriptorImpl descriptor = (InstanceFieldDescriptorImpl) nodeDescriptor; assert !descriptor.isPrimitive(); Instance instance = (Instance) descriptor.getValueData(); if (instance != null && instance.getDistanceToGcRoot() != Integer.MAX_VALUE) { append(String.valueOf(instance.getTotalRetainedSize()), SimpleTextAttributes.REGULAR_ATTRIBUTES); } } setTextAlign(SwingConstants.RIGHT); } })); //noinspection NullableProblems builder.setTreeSorter(new ColumnTreeBuilder.TreeSorter<DebuggerTreeNodeImpl>() { @Override public void sort(@NotNull Comparator<DebuggerTreeNodeImpl> comparator, @NotNull SortOrder sortOrder) { if (myComparator != comparator && mySortOrder != sortOrder) { myComparator = comparator; mySortOrder = sortOrder; TreeBuilder mutableModel = myDebuggerTree.getMutableModel(); DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl) mutableModel.getRoot(); sortTree(root); mySelectionModel.setSelectionLocked(true); TreePath selectionPath = myDebuggerTree.getSelectionPath(); mutableModel.nodeStructureChanged(root); myDebuggerTree.setSelectionPath(selectionPath); myDebuggerTree.scrollPathToVisible(selectionPath); mySelectionModel.setSelectionLocked(false); } } }); myColumnTree = builder.build(); } public void addGoToInstanceListener(@NotNull GoToInstanceListener listener) { myGoToInstanceAction.addListener(listener); } @NotNull public JComponent getComponent() { return myColumnTree; } public void requestFocus() { IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { IdeFocusManager.getGlobalInstance().requestFocus(myDebuggerTree, true); }); } private void sortTree(@NotNull DebuggerTreeNodeImpl node) { if (myComparator == null) { return; } // We don't want to accidentally build children, so we have to get the raw children instead. Enumeration e = node.rawChildren(); if (e.hasMoreElements()) { //noinspection unchecked ArrayList<DebuggerTreeNodeImpl> builtChildren = Collections.list(e); // First check if there's an expansion node. Remove if there is, and add it back at the end. DebuggerTreeNodeImpl expansionNode = builtChildren.get(builtChildren.size() - 1); if (expansionNode.getDescriptor() instanceof ExpansionDescriptorImpl) { builtChildren.remove(builtChildren.size() - 1); } else { expansionNode = null; } Collections.sort(builtChildren, myComparator); node.removeAllChildren(); // Remove children after sorting, since the sort may depend on the parent information. for (DebuggerTreeNodeImpl childNode : builtChildren) { node.add(childNode); sortTree(childNode); } if (expansionNode != null) { node.add(expansionNode); } } } private int getDefaultOrdering(@NotNull DebuggerTreeNodeImpl a, @NotNull DebuggerTreeNodeImpl b) { NodeDescriptorImpl parentDescriptor = a.getParent().getDescriptor(); if (parentDescriptor instanceof InstanceFieldDescriptorImpl) { Instance parentInstance = ((InstanceFieldDescriptorImpl) parentDescriptor).getInstance(); if (parentInstance instanceof ArrayInstance) { return getMemoryOrderingSortResult(a, b); } } else if (parentDescriptor instanceof ContainerDescriptorImpl) { return getMemoryOrderingSortResult(a, b); } return a.getDescriptor().getLabel().compareTo(b.getDescriptor().getLabel()); } private int getMemoryOrderingSortResult(@NotNull DebuggerTreeNodeImpl a, @NotNull DebuggerTreeNodeImpl b) { return (((HprofFieldDescriptorImpl) a.getDescriptor()).getMemoryOrdering() - ((HprofFieldDescriptorImpl) b.getDescriptor()).getMemoryOrdering()) ^ (mySortOrder == SortOrder.ASCENDING ? 1 : -1); } @Nullable private DebuggerTreeNodeImpl expandMoreNodesUntilFound(@NotNull DebuggerTreeNodeImpl node, @NotNull Instance targetInstance) { if (node.getDescriptor() instanceof ContainerDescriptorImpl) { int startIndex = 0; if (node.getChildCount() > 0) { DebuggerTreeNodeImpl lastNode = (DebuggerTreeNodeImpl) node.getChildAt(node.getChildCount() - 1); if (lastNode.getDescriptor() instanceof ExpansionDescriptorImpl) { lastNode.removeFromParent(); startIndex = node.getChildCount() - 1; } else { for (int scanIndex = 0; scanIndex < node.getChildCount(); ++scanIndex) { DebuggerTreeNodeImpl scanNode = (DebuggerTreeNodeImpl) node.getChildAt(scanIndex); NodeDescriptor nodeDescriptor = scanNode.getDescriptor(); if (nodeDescriptor instanceof InstanceFieldDescriptorImpl && ((InstanceFieldDescriptorImpl) nodeDescriptor).getInstance() == targetInstance) { return scanNode; } } return null; } } int maxSize = ((ContainerDescriptorImpl) node.getDescriptor()).getInstances().size(); while (startIndex < maxSize) { addContainerChildren(node, startIndex, false); for (int scanIndex = startIndex; scanIndex < node.getChildCount(); ++scanIndex) { DebuggerTreeNodeImpl scanNode = (DebuggerTreeNodeImpl) node.getChildAt(scanIndex); NodeDescriptor nodeDescriptor = scanNode.getDescriptor(); if (nodeDescriptor instanceof InstanceFieldDescriptorImpl && ((InstanceFieldDescriptorImpl) nodeDescriptor).getInstance() == targetInstance) { addExpansionNode(node, node.getChildCount(), maxSize); return scanNode; } } startIndex = node.getChildCount(); } } return null; } private void addContainerChildren(@NotNull DebuggerTreeNodeImpl node, int startIndex, boolean addExpansionNode) { ContainerDescriptorImpl containerDescriptor = (ContainerDescriptorImpl) node.getDescriptor(); List<Instance> instances = containerDescriptor.getInstances(); List<HprofFieldDescriptorImpl> descriptors = new ArrayList<>(NODES_PER_EXPANSION); int currentIndex = startIndex; int limit = currentIndex + NODES_PER_EXPANSION; for (int loopCounter = currentIndex; loopCounter < instances.size() && currentIndex < limit; ++loopCounter) { Instance instance = instances.get(loopCounter); if (myHeap.getInstance(instance.getId()) != null) { descriptors.add(new InstanceFieldDescriptorImpl(myDebuggerTree.getProject(), new Field(Type.OBJECT, Integer.toString(currentIndex)), instance, currentIndex)); ++currentIndex; } } HprofFieldDescriptorImpl.batchUpdateRepresentation(descriptors, myDebugProcess.getManagerThread(), myDummySuspendContext); for (HprofFieldDescriptorImpl descriptor : descriptors) { node.add(DebuggerTreeNodeImpl.createNodeNoUpdate(myDebuggerTree, descriptor)); } if (currentIndex == limit && addExpansionNode) { addExpansionNode(node, limit, instances.size()); } } private void addExpansionNode(@NotNull DebuggerTreeNodeImpl node, int currentIndex, int maxSize) { node.add(DebuggerTreeNodeImpl.createNodeNoUpdate(myDebuggerTree, new ExpansionDescriptorImpl("instances", currentIndex, maxSize))); } private void addChildren(@NotNull DebuggerTreeNodeImpl node, @Nullable Field field, @Nullable Instance instance) { addChildren(node, field, instance, 0); } private void addChildren(@NotNull DebuggerTreeNodeImpl node, @Nullable Field field, @Nullable Instance instance, int arrayStartIndex) { if (instance == null) { return; } // These local variables are used for adding an expansion node for array tree node expansion. int currentArrayIndex = arrayStartIndex; int limit = currentArrayIndex + NODES_PER_EXPANSION; int arrayLength = 0; List<HprofFieldDescriptorImpl> descriptors; if (instance instanceof ClassInstance) { ClassInstance classInstance = (ClassInstance) instance; descriptors = new ArrayList<>(classInstance.getValues().size()); int i = 0; for (ClassInstance.FieldValue entry : classInstance.getValues()) { if (entry.getField().getType() == Type.OBJECT) { descriptors.add(new InstanceFieldDescriptorImpl(myDebuggerTree.getProject(), entry.getField(), (Instance) entry.getValue(), i)); } else { descriptors.add(new PrimitiveFieldDescriptorImpl(myDebuggerTree.getProject(), entry.getField(), entry.getValue(), i)); } ++i; } } else if (instance instanceof ArrayInstance) { assert (field != null); ArrayInstance arrayInstance = (ArrayInstance) instance; Object[] values = arrayInstance.getValues(); descriptors = new ArrayList<>(values.length); arrayLength = values.length; if (arrayInstance.getArrayType() == Type.OBJECT) { while (currentArrayIndex < arrayLength && currentArrayIndex < limit) { descriptors.add(new InstanceFieldDescriptorImpl(myDebuggerTree.getProject(), new Field(arrayInstance.getArrayType(), String.valueOf(currentArrayIndex)), (Instance) values[currentArrayIndex], currentArrayIndex)); ++currentArrayIndex; } } else { while (currentArrayIndex < arrayLength && currentArrayIndex < limit) { descriptors.add(new PrimitiveFieldDescriptorImpl(myDebuggerTree.getProject(), new Field(arrayInstance.getArrayType(), String.valueOf(currentArrayIndex)), values[currentArrayIndex], currentArrayIndex)); ++currentArrayIndex; } } } else { throw new RuntimeException("Unimplemented Instance type in addChildren."); } HprofFieldDescriptorImpl.batchUpdateRepresentation(descriptors, myDebugProcess.getManagerThread(), myDummySuspendContext); for (HprofFieldDescriptorImpl descriptor : descriptors) { node.add(DebuggerTreeNodeImpl.createNodeNoUpdate(myDebuggerTree, descriptor)); } if (currentArrayIndex == limit) { node.add(DebuggerTreeNodeImpl.createNodeNoUpdate(myDebuggerTree, new ExpansionDescriptorImpl("array elements", limit, arrayLength))); } } @Nullable @Override public Object getData(@NonNls String dataId) { if (CommonDataKeys.NAVIGATABLE_ARRAY.is(dataId)) { return getTargetFiles(); } else if (CommonDataKeys.PROJECT.is(dataId)) { return myProject; } else if (SELECTED_CLASS_INSTANCE.is(dataId)) { Instance instance = mySelectionModel.getInstance(); return instance instanceof ClassInstance ? (ClassInstance) instance : null; } else if (InstanceReferenceTreeView.NAVIGATABLE_INSTANCE.is(dataId)) { Object node = myDebuggerTree.getSelectionPath().getLastPathComponent(); NodeDescriptorImpl nodeDescriptor = node instanceof DebuggerTreeNodeImpl ? ((DebuggerTreeNodeImpl) node).getDescriptor() : null; return nodeDescriptor instanceof InstanceFieldDescriptorImpl ? ((InstanceFieldDescriptorImpl) nodeDescriptor).getInstance() : null; } return null; } @Nullable private PsiClassNavigation[] getTargetFiles() { Object node = myDebuggerTree.getSelectionPath().getLastPathComponent(); String className = null; if (node instanceof DebuggerTreeNodeImpl) { NodeDescriptorImpl nodeDescriptor = ((DebuggerTreeNodeImpl) node).getDescriptor(); if (nodeDescriptor instanceof InstanceFieldDescriptorImpl) { Instance instance = ((InstanceFieldDescriptorImpl) nodeDescriptor).getInstance(); if (instance != null) { if (instance instanceof ClassObj) { className = ((ClassObj) instance).getClassName(); } else { className = instance.getClassObj().getClassName(); if (instance instanceof ArrayInstance) { className = className.replace("[]", ""); } } } } } return PsiClassNavigation.getNavigationForClass(myProject, className); } @Override public void dispose() { myDebugProcess.stop(true); myDebugProcess.dispose(); } }