MemoryInstanceDetailsView extends AspectObserver { @VisibleForTesting @NotNull JTree buildTree(@NotNull InstanceObject instance) { Comparator<MemoryObjectTreeNode<ValueObject>> comparator = null; if (myReferenceTree != null && myReferenceTree.getModel() != null && myReferenceTree.getModel().getRoot() != null) { Object root = myReferenceTree.getModel().getRoot(); if (root instanceof ReferenceTreeNode) { comparator = ((ReferenceTreeNode)root).getComparator(); } } final ReferenceTreeNode treeRoot = new ReferenceTreeNode(instance); treeRoot.expandNode(); if (comparator != null) { treeRoot.sort(comparator); } final DefaultTreeModel treeModel = new DefaultTreeModel(treeRoot); final JTree tree = new JTree(treeModel); int defaultFontHeight = tree.getFontMetrics(tree.getFont()).getHeight(); tree.setRowHeight(defaultFontHeight + ROW_HEIGHT_PADDING); tree.setBorder(TABLE_ROW_BORDER); tree.setRootVisible(true); tree.setShowsRootHandles(true); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.addTreeExpansionListener(new TreeExpansionListener() { @Override public void treeExpanded(TreeExpansionEvent event) { TreePath path = event.getPath(); assert path.getLastPathComponent() instanceof MemoryObjectTreeNode; ReferenceTreeNode treeNode = (ReferenceTreeNode)path.getLastPathComponent(); treeNode.expandNode(); treeModel.nodeStructureChanged(treeNode); } @Override public void treeCollapsed(TreeExpansionEvent event) { } }); ContextMenuInstaller contextMenuInstaller = myIdeProfilerComponents.createContextMenuInstaller(); contextMenuInstaller.installNavigationContextMenu(tree, myStage.getStudioProfilers().getIdeServices().getCodeNavigator(), () -> { TreePath selection = tree.getSelectionPath(); if (selection == null) { return null; } MemoryObject memoryObject = ((MemoryObjectTreeNode)selection.getLastPathComponent()).getAdapter(); if (memoryObject instanceof InstanceObject) { return new CodeLocation.Builder(((InstanceObject)memoryObject).getClassEntry().getClassName()).build(); } else { assert memoryObject instanceof ReferenceObject; return new CodeLocation.Builder(((ReferenceObject)memoryObject).getReferenceInstance().getClassEntry().getClassName()).build(); } }); contextMenuInstaller.installGenericContextMenu(tree, new ContextMenuItem() { @NotNull @Override public String getText() { return "Go to Instance"; } @Nullable @Override public Icon getIcon() { return null; } @Override public boolean isEnabled() { return tree.getSelectionPath() != null; } @Override public void run() { CaptureObject captureObject = myStage.getSelectedCapture(); TreePath selection = tree.getSelectionPath(); assert captureObject != null && selection != null; MemoryObject memoryObject = ((MemoryObjectTreeNode)selection.getLastPathComponent()).getAdapter(); if (memoryObject instanceof InstanceObject) { assert memoryObject == myStage.getSelectedInstanceObject(); } else { assert memoryObject instanceof ReferenceObject; InstanceObject targetInstance = ((ReferenceObject)memoryObject).getReferenceInstance(); HeapSet heapSet = captureObject.getHeapSet(targetInstance.getHeapId()); assert heapSet != null; myStage.selectHeapSet(heapSet); ClassifierSet classifierSet = heapSet.findContainingClassifierSet(targetInstance); assert classifierSet instanceof ClassSet; myStage.selectClassSet((ClassSet)classifierSet); myStage.selectInstanceObject(targetInstance); } } }); tree.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { if (tree.getSelectionCount() == 0 && tree.getRowCount() != 0) { tree.setSelectionRow(0); } } }); return tree; } MemoryInstanceDetailsView(@NotNull MemoryProfilerStage stage, @NotNull IdeProfilerComponents ideProfilerComponents);   }
@SuppressWarnings("unchecked") @Test public void buildTreeTest() throws Exception { FakeInstanceObject fakeInstance1 = new FakeInstanceObject.Builder(myFakeCaptureObject, 1, "DUMMY_CLASS").setName("fake1").setFields(Collections.singletonList("mField")) .build(); FakeInstanceObject fakeInstance2 = new FakeInstanceObject.Builder(myFakeCaptureObject, 1, "DUMMY_CLASS").setName("fake2").setFields(Collections.singletonList("mField")) .build(); FakeInstanceObject fakeInstance3 = new FakeInstanceObject.Builder(myFakeCaptureObject, 1, "DUMMY_CLASS").setName("fake3").setFields(Collections.singletonList("mField")) .build(); FakeInstanceObject fakeInstance4 = new FakeInstanceObject.Builder(myFakeCaptureObject, 1, "DUMMY_CLASS").setName("fake4").setFields(Collections.singletonList("mField")) .build(); FakeInstanceObject fakeInstance5 = new FakeInstanceObject.Builder(myFakeCaptureObject, 1, "DUMMY_CLASS").setName("fake5").setFields(Collections.singletonList("mField")) .build(); FakeInstanceObject fakeRootObject = new FakeInstanceObject.Builder(myFakeCaptureObject, 2, "DUMMY_ROOT").setName("FakeRoot").build(); fakeInstance1.setFieldValue("mField", OBJECT, fakeRootObject); fakeInstance2.setFieldValue("mField", OBJECT, fakeInstance1); fakeInstance3.setFieldValue("mField", OBJECT, fakeInstance1); fakeInstance4.setFieldValue("mField", OBJECT, fakeInstance3); fakeInstance5.setFieldValue("mField", OBJECT, fakeRootObject); myFakeCaptureObject .addInstanceObjects(ImmutableSet.of(fakeInstance1, fakeInstance2, fakeInstance3, fakeInstance4, fakeInstance5, fakeRootObject)); myStage.selectCaptureDuration( new CaptureDurationData<>(1, false, false, new CaptureEntry<CaptureObject>(new Object(), () -> myFakeCaptureObject)), null); JTree tree = myDetailsView.buildTree(fakeRootObject); DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel(); assertNotNull(treeModel); MemoryObjectTreeNode<ValueObject> treeRoot = (MemoryObjectTreeNode<ValueObject>)treeModel.getRoot(); assertNotNull(treeRoot); assertEquals(fakeRootObject, treeRoot.getAdapter()); assertEquals(2, treeRoot.getChildCount()); MemoryObjectTreeNode ref1 = treeRoot.getChildren().stream().filter(child -> child.getAdapter() instanceof ReferenceObject && "fake1".equals(((ReferenceObject)child.getAdapter()) .getReferenceInstance().getName())) .findFirst().orElse(null); assertNotNull(ref1); assertEquals(fakeInstance1, ((ReferenceObject)ref1.getAdapter()).getReferenceInstance()); MemoryObjectTreeNode ref5 = treeRoot.getChildren().stream().filter(child -> child.getAdapter() instanceof ReferenceObject && "fake5".equals(((ReferenceObject)child.getAdapter()) .getReferenceInstance().getName())) .findFirst().orElse(null); assertNotNull(ref5); assertEquals(fakeInstance5, ((ReferenceObject)ref5.getAdapter()).getReferenceInstance()); assertEquals(2, ref1.getChildCount()); assertEquals(0, ref5.getChildCount()); MemoryObjectTreeNode<ReferenceObject> ref2 = (MemoryObjectTreeNode<ReferenceObject>)ref1.getChildAt(0); MemoryObjectTreeNode<ReferenceObject> ref3 = (MemoryObjectTreeNode<ReferenceObject>)ref1.getChildAt(1); assertEquals(fakeInstance2, ref2.getAdapter().getReferenceInstance()); assertEquals(fakeInstance3, ref3.getAdapter().getReferenceInstance()); TreePath path = new TreePath(new Object[]{treeRoot, ref1}); tree.expandPath(path); assertEquals(0, ref2.getChildCount()); assertEquals(1, ref3.getChildCount()); assertEquals(fakeInstance4, ((MemoryObjectTreeNode<ReferenceObject>)ref3.getChildAt(0)).getAdapter().getReferenceInstance()); }