/******************************************************************************* | |
* Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License 2.0 | |
* which accompanies this distribution, and is available at | |
* https://www.eclipse.org/legal/epl-2.0/ | |
* | |
* SPDX-License-Identifier: EPL-2.0 | |
* | |
* Contributors: | |
* SAP AG - initial API and implementation | |
* IBM Corporation - accessibility related fixes | |
* Chris Grindstaff | |
*******************************************************************************/ | |
package org.eclipse.mat.ui.snapshot.views.inspector; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.LinkedList; | |
import java.util.List; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
import org.eclipse.core.runtime.IStatus; | |
import org.eclipse.core.runtime.Platform; | |
import org.eclipse.core.runtime.Status; | |
import org.eclipse.core.runtime.jobs.Job; | |
import org.eclipse.jface.action.Action; | |
import org.eclipse.jface.action.IAction; | |
import org.eclipse.jface.action.IToolBarManager; | |
import org.eclipse.jface.layout.GridDataFactory; | |
import org.eclipse.jface.layout.GridLayoutFactory; | |
import org.eclipse.jface.layout.TableColumnLayout; | |
import org.eclipse.jface.layout.TreeColumnLayout; | |
import org.eclipse.jface.resource.FontDescriptor; | |
import org.eclipse.jface.resource.ImageDescriptor; | |
import org.eclipse.jface.resource.JFaceResources; | |
import org.eclipse.jface.viewers.ColumnWeightData; | |
import org.eclipse.jface.viewers.IFontProvider; | |
import org.eclipse.jface.viewers.ISelection; | |
import org.eclipse.jface.viewers.ISelectionChangedListener; | |
import org.eclipse.jface.viewers.IStructuredContentProvider; | |
import org.eclipse.jface.viewers.IStructuredSelection; | |
import org.eclipse.jface.viewers.ITreeContentProvider; | |
import org.eclipse.jface.viewers.LabelProvider; | |
import org.eclipse.jface.viewers.SelectionChangedEvent; | |
import org.eclipse.jface.viewers.StructuredSelection; | |
import org.eclipse.jface.viewers.StructuredViewer; | |
import org.eclipse.jface.viewers.TableViewer; | |
import org.eclipse.jface.viewers.TreeViewer; | |
import org.eclipse.jface.viewers.Viewer; | |
import org.eclipse.mat.SnapshotException; | |
import org.eclipse.mat.query.IContextObject; | |
import org.eclipse.mat.query.IContextObjectSet; | |
import org.eclipse.mat.snapshot.ISnapshot; | |
import org.eclipse.mat.snapshot.OQL; | |
import org.eclipse.mat.snapshot.model.GCRootInfo; | |
import org.eclipse.mat.snapshot.model.IClass; | |
import org.eclipse.mat.snapshot.model.IInstance; | |
import org.eclipse.mat.snapshot.model.IObject; | |
import org.eclipse.mat.snapshot.model.IObjectArray; | |
import org.eclipse.mat.snapshot.model.IPrimitiveArray; | |
import org.eclipse.mat.snapshot.model.ObjectReference; | |
import org.eclipse.mat.ui.MemoryAnalyserPlugin; | |
import org.eclipse.mat.ui.Messages; | |
import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter; | |
import org.eclipse.mat.ui.accessibility.AccessibleToolbarAdapter; | |
import org.eclipse.mat.ui.snapshot.ImageHelper; | |
import org.eclipse.mat.ui.snapshot.editor.HeapEditor; | |
import org.eclipse.mat.ui.snapshot.editor.ISnapshotEditorInput; | |
import org.eclipse.mat.ui.util.Copy; | |
import org.eclipse.mat.ui.util.PopupMenu; | |
import org.eclipse.mat.ui.util.QueryContextMenu; | |
import org.eclipse.mat.util.MessageUtil; | |
import org.eclipse.swt.SWT; | |
import org.eclipse.swt.custom.CTabFolder; | |
import org.eclipse.swt.custom.CTabItem; | |
import org.eclipse.swt.custom.SashForm; | |
import org.eclipse.swt.custom.StyledText; | |
import org.eclipse.swt.events.MenuAdapter; | |
import org.eclipse.swt.events.MenuEvent; | |
import org.eclipse.swt.events.PaintEvent; | |
import org.eclipse.swt.events.PaintListener; | |
import org.eclipse.swt.events.SelectionAdapter; | |
import org.eclipse.swt.events.SelectionEvent; | |
import org.eclipse.swt.graphics.Color; | |
import org.eclipse.swt.graphics.Font; | |
import org.eclipse.swt.graphics.Image; | |
import org.eclipse.swt.graphics.ImageData; | |
import org.eclipse.swt.graphics.PaletteData; | |
import org.eclipse.swt.graphics.RGB; | |
import org.eclipse.swt.widgets.Composite; | |
import org.eclipse.swt.widgets.Menu; | |
import org.eclipse.swt.widgets.MenuItem; | |
import org.eclipse.swt.widgets.Table; | |
import org.eclipse.swt.widgets.TableColumn; | |
import org.eclipse.swt.widgets.ToolBar; | |
import org.eclipse.swt.widgets.ToolItem; | |
import org.eclipse.swt.widgets.Tree; | |
import org.eclipse.swt.widgets.TreeColumn; | |
import org.eclipse.ui.IPartListener; | |
import org.eclipse.ui.IWorkbenchPage; | |
import org.eclipse.ui.IWorkbenchPart; | |
import org.eclipse.ui.actions.ActionFactory; | |
import org.eclipse.ui.part.ViewPart; | |
public class InspectorView extends ViewPart implements IPartListener, ISelectionChangedListener | |
{ | |
private HeapEditor editor; | |
/* package */ISnapshot snapshot; | |
private Composite top; | |
private Composite visualViewer; | |
private TableViewer topTableViewer; | |
private CTabFolder tabFolder; | |
private TableViewer attributesTable; | |
private TableViewer staticsTable; | |
private TreeViewer classHierarchyTree; | |
private StyledText resolvedValue; | |
private boolean pinSelection = false; | |
private Font font; | |
private List<Menu> contextMenus = new ArrayList<Menu>(); | |
boolean keepInSync = true; | |
/* package */static class BaseNode | |
{ | |
int objectId; | |
long addr; | |
public BaseNode(int objectId) | |
{ | |
this(objectId, 0); | |
} | |
public BaseNode(int objectId, long addr) | |
{ | |
this.objectId = objectId; | |
this.addr = addr; | |
} | |
static int objectID(IObject object) | |
{ | |
if (object == null) | |
return -1; | |
try | |
{ | |
return object.getObjectId(); | |
} | |
catch (RuntimeException e) | |
{ | |
if (e.getCause() instanceof SnapshotException) | |
return -1; | |
throw e; | |
} | |
} | |
} | |
private static class ObjectNode extends BaseNode | |
{ | |
String label; | |
int imageType; | |
public ObjectNode(IObject object) | |
{ | |
super(objectID(object), object.getObjectAddress()); | |
this.label = object.getTechnicalName(); | |
this.imageType = ImageHelper.getType(object); | |
} | |
public String getLabel() | |
{ | |
return label; | |
} | |
public int getImageType() | |
{ | |
return imageType; | |
} | |
} | |
private static class TopTableLabelProvider extends LabelProvider | |
{ | |
@Override | |
public String getText(Object element) | |
{ | |
if (element instanceof InfoItem) | |
return ((InfoItem) element).getText(); | |
else if (element instanceof ObjectNode) | |
return ((ObjectNode) element).getLabel(); | |
else if (element instanceof GCRootInfo[]) | |
return Messages.InspectorView_GCroot + GCRootInfo.getTypeSetAsString((GCRootInfo[]) element); | |
else | |
return "";//$NON-NLS-1$ | |
} | |
@Override | |
public Image getImage(Object element) | |
{ | |
if (element instanceof InfoItem) | |
return MemoryAnalyserPlugin.getDefault().getImage(((InfoItem) element).getDescriptor()); | |
else if (element instanceof ObjectNode) | |
return ImageHelper.getImage(((ObjectNode) element).getImageType()); | |
else if (element instanceof GCRootInfo[]) | |
return MemoryAnalyserPlugin.getImage(ImageHelper.Decorations.GC_ROOT); | |
else | |
return null; | |
} | |
} | |
private static final class TableContentProvider implements IStructuredContentProvider | |
{ | |
Object[] elements; | |
public Object[] getElements(Object inputElement) | |
{ | |
return elements; | |
} | |
public void dispose() | |
{} | |
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) | |
{ | |
if (newInput instanceof Collection<?>) | |
{ | |
this.elements = ((Collection<?>) newInput).toArray(); | |
} | |
else | |
{ | |
this.elements = (Object[]) newInput; | |
} | |
} | |
} | |
public static class InfoItem extends BaseNode | |
{ | |
private ImageDescriptor descriptor; | |
private String text; | |
public InfoItem(ImageDescriptor descriptor, String text) | |
{ | |
this(null, descriptor, text); | |
} | |
public InfoItem(IObject object, ImageDescriptor descriptor, String text) | |
{ | |
super(objectID(object), object != null ? object.getObjectAddress() : 0); | |
this.descriptor = descriptor; | |
this.text = text; | |
} | |
public ImageDescriptor getDescriptor() | |
{ | |
return descriptor; | |
} | |
public String getText() | |
{ | |
return text; | |
} | |
} | |
private class MenuListener extends MenuAdapter | |
{ | |
private Menu menu; | |
private StructuredViewer viewer; | |
public MenuListener(Menu menu, StructuredViewer viewer) | |
{ | |
this.menu = menu; | |
this.viewer = viewer; | |
} | |
@Override | |
public void menuShown(MenuEvent e) | |
{ | |
MenuItem[] items = menu.getItems(); | |
for (int ii = 0; ii < items.length; ii++) | |
items[ii].dispose(); | |
PopupMenu popup = new PopupMenu(); | |
fillContextMenu(popup, viewer); | |
popup.addToMenu(getViewSite().getActionBars().getStatusLineManager(), menu); | |
} | |
} | |
private static class HierarchyTreeContentProvider implements ITreeContentProvider | |
{ | |
LinkedList<IClass> supers; | |
public Object[] getChildren(Object element) | |
{ | |
int index = supers.indexOf(element); | |
if (index >= 0 && index + 1 < supers.size()) | |
return new Object[] { supers.get(index + 1) }; | |
return ((IClass) element).getSubclasses().toArray(); | |
} | |
public IClass getParent(Object element) | |
{ | |
return ((IClass) element).getSuperClass(); | |
} | |
public boolean hasChildren(Object element) | |
{ | |
return !((IClass) element).getSubclasses().isEmpty(); | |
} | |
public Object[] getElements(Object inputElement) | |
{ | |
if (supers.isEmpty()) | |
return new Object[0]; | |
return new Object[] { supers.get(0) }; | |
} | |
public void dispose() | |
{} | |
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) | |
{ | |
supers = new LinkedList<IClass>(); | |
if (newInput instanceof IClass[]) | |
{ | |
IClass[] input = (IClass[]) newInput; | |
supers = new LinkedList<IClass>(); | |
supers.add(input[0]); | |
while (input[0].hasSuperClass()) | |
{ | |
input[0] = input[0].getSuperClass(); | |
supers.addFirst(input[0]); | |
} | |
} | |
} | |
} | |
private class HierarchyLabelProvider extends LabelProvider implements IFontProvider | |
{ | |
private int classId; | |
public HierarchyLabelProvider(int classId) | |
{ | |
super(); | |
this.classId = classId; | |
} | |
@Override | |
public Image getImage(Object element) | |
{ | |
return (element instanceof IClass) ? ImageHelper.getImage(ImageHelper.Type.CLASS) : null; | |
} | |
@Override | |
public String getText(Object element) | |
{ | |
return (element instanceof IClass) ? ((IClass) element).getName() : "";//$NON-NLS-1$ | |
} | |
public Font getFont(Object element) | |
{ | |
if (element instanceof IClass && ((IClass) element).getObjectId() == classId) | |
return font; | |
return null; | |
} | |
} | |
// ////////////////////////////////////////////////////////////// | |
// view construction | |
// ////////////////////////////////////////////////////////////// | |
@Override | |
public void createPartControl(final Composite parent) | |
{ | |
SashForm form = new SashForm(parent, SWT.VERTICAL); | |
GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0).spacing(1, 1).applyTo(form); | |
top = new Composite(form, SWT.TOP); | |
GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0).spacing(1, 1).applyTo(top); | |
IToolBarManager mgr = getViewSite().getActionBars().getToolBarManager(); | |
mgr.add(createSyncAction()); | |
FontDescriptor fontDescriptor = FontDescriptor.createFrom(JFaceResources.getDefaultFont()); | |
fontDescriptor = fontDescriptor.setStyle(SWT.BOLD); | |
this.font = fontDescriptor.createFont(top.getDisplay()); | |
createTopTable(top); | |
createTabFolder(top); | |
createVisualViewer(form); | |
form.setWeights(new int[] { 95, 5 }); | |
// add page listener | |
getSite().getPage().addPartListener(this); | |
hookContextMenu(); | |
showBootstrapPart(); | |
} | |
private void createVisualViewer(Composite parent) | |
{ | |
visualViewer = new Composite(parent, SWT.TOP); | |
visualViewer.addPaintListener(new PaintListener() | |
{ | |
public void paintControl(PaintEvent paintEvent) | |
{ | |
Object toShow = visualViewer.getData("toShow");//$NON-NLS-1$ | |
if (toShow == null) | |
return; | |
if (toShow instanceof Image) | |
{ | |
paintEvent.gc.drawImage((Image) toShow, 0, 0); | |
} | |
else if (toShow instanceof RGB) | |
{ | |
Color color = new Color(paintEvent.display, (RGB) toShow); | |
paintEvent.gc.setBackground(color); | |
paintEvent.gc.fillRectangle(0, 0, visualViewer.getSize().x, visualViewer.getSize().y); | |
color.dispose(); | |
} | |
} | |
}); | |
visualViewer.setVisible(false); | |
} | |
private Action createSyncAction() | |
{ | |
Action syncAction = new Action(null, IAction.AS_CHECK_BOX) | |
{ | |
@Override | |
public void run() | |
{ | |
if (!keepInSync) | |
{ | |
showBootstrapPart(); | |
if (editor != null) | |
updateOnSelection(editor.getSelection()); | |
keepInSync = true; | |
} | |
else | |
{ | |
keepInSync = false; | |
} | |
this.setChecked(!keepInSync); | |
} | |
}; | |
syncAction.setImageDescriptor(MemoryAnalyserPlugin | |
.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.SYNCED)); | |
syncAction.setToolTipText(Messages.InspectorView_LinkWithSnapshot); | |
return syncAction; | |
} | |
private void createTopTable(Composite parent) | |
{ | |
Composite composite = new Composite(parent, SWT.NONE); | |
topTableViewer = new TableViewer(composite, SWT.FULL_SELECTION | SWT.MULTI); | |
Table table = topTableViewer.getTable(); | |
AccessibleCompositeAdapter.access(table); | |
TableColumnLayout columnLayout = new TableColumnLayout(); | |
composite.setLayout(columnLayout); | |
TableColumn column = new TableColumn(table, SWT.LEFT); | |
columnLayout.setColumnData(column, new ColumnWeightData(100, 10)); | |
// on win32, item height is reported too low the first time around | |
int itemHeight = table.getItemHeight() + 1; | |
if (itemHeight < 17) | |
itemHeight = 17; | |
int scrollbarHeight = 2; | |
if (!Platform.OS_WIN32.equals(Platform.getOS()))//$NON-NLS-1$ | |
scrollbarHeight = table.getHorizontalBar().getSize().y; | |
int detailsHeight = 9 * itemHeight + scrollbarHeight; | |
table.setHeaderVisible(false); | |
table.setLinesVisible(false); | |
topTableViewer.setLabelProvider(new TopTableLabelProvider()); | |
topTableViewer.setContentProvider(new TableContentProvider()); | |
GridDataFactory.fillDefaults().hint(SWT.DEFAULT, detailsHeight)// | |
.grab(true, false).applyTo(composite); | |
} | |
private void createTabFolder(Composite parent) | |
{ | |
tabFolder = new CTabFolder(parent, SWT.TOP | SWT.FLAT); | |
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(tabFolder); | |
ToolBar toolBar = new ToolBar(tabFolder, SWT.HORIZONTAL | SWT.FLAT); | |
// Add custom AccessibleAdapter, passing in associated ToolBar. | |
toolBar.getAccessible().addAccessibleListener(new AccessibleToolbarAdapter(toolBar) ); | |
tabFolder.setTopRight(toolBar); | |
// set the height of the tab to display the tool bar correctly | |
tabFolder.setTabHeight(Math.max(toolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT).y, tabFolder.getTabHeight())); | |
final ToolItem pinItem = new ToolItem(toolBar, SWT.CHECK); | |
pinItem.setImage(MemoryAnalyserPlugin.getImage(MemoryAnalyserPlugin.ISharedImages.PINNED)); | |
pinItem.setToolTipText(Messages.InspectorView_PinTab); | |
pinItem.addSelectionListener(new SelectionAdapter() | |
{ | |
@Override | |
public void widgetSelected(SelectionEvent e) | |
{ | |
pinSelection = !pinSelection; | |
pinItem.setSelection(pinSelection); | |
} | |
}); | |
toolBar.pack(); | |
final CTabItem staticsTab = new CTabItem(tabFolder, SWT.NULL); | |
staticsTab.setText(Messages.InspectorView_Statics); | |
staticsTable = createTable(tabFolder); | |
staticsTab.setControl(staticsTable.getTable().getParent()); | |
CTabItem instancesTab = new CTabItem(tabFolder, SWT.NULL); | |
instancesTab.setText(Messages.InspectorView_Attributes); | |
attributesTable = createTable(tabFolder); | |
instancesTab.setControl(attributesTable.getTable().getParent()); | |
CTabItem classHierarchyTab = new CTabItem(tabFolder, SWT.NULL); | |
classHierarchyTab.setText(Messages.InspectorView_ClassHierarchy); | |
classHierarchyTree = createHierarchyTree(tabFolder); | |
classHierarchyTab.setControl(classHierarchyTree.getTree().getParent()); | |
CTabItem valueTab = new CTabItem(tabFolder, SWT.NULL); | |
valueTab.setText(Messages.InspectorView_Value); | |
resolvedValue = createValue(tabFolder); | |
valueTab.setControl(resolvedValue); | |
getViewSite().getActionBars().setGlobalActionHandler(ActionFactory.COPY.getId(), new Action() | |
{ | |
@Override | |
public void run() | |
{ | |
if (topTableViewer.getControl().isFocusControl()) | |
{ | |
Copy.copyToClipboard(topTableViewer.getControl()); | |
} | |
else | |
{ | |
int s = tabFolder.getSelectionIndex(); | |
switch (s) | |
{ | |
case 0: | |
Copy.copyToClipboard(staticsTable.getControl()); | |
break; | |
case 1: | |
Copy.copyToClipboard(attributesTable.getControl()); | |
break; | |
case 2: | |
Copy.copyToClipboard(classHierarchyTree.getControl()); | |
break; | |
case 3: | |
String buffer = resolvedValue.getSelectionText(); | |
Copy.copyToClipboard(buffer, resolvedValue.getDisplay()); | |
break; | |
default: | |
break; | |
} | |
} | |
} | |
}); | |
tabFolder.setSelection(0); | |
} | |
private StyledText createValue(CTabFolder parent) | |
{ | |
StyledText ret = new StyledText(parent, SWT.READ_ONLY | SWT.MULTI | SWT.WRAP); | |
return ret; | |
} | |
private TreeViewer createHierarchyTree(CTabFolder parent) | |
{ | |
Composite composite = new Composite(parent, SWT.NONE); | |
TreeViewer classHierarchyTree = new TreeViewer(composite, SWT.FULL_SELECTION | SWT.MULTI); | |
classHierarchyTree.setContentProvider(new HierarchyTreeContentProvider()); | |
classHierarchyTree.setLabelProvider(new HierarchyLabelProvider(-1)); | |
Tree tree = classHierarchyTree.getTree(); | |
AccessibleCompositeAdapter.access(tree); | |
TreeColumnLayout columnLayout = new TreeColumnLayout(); | |
composite.setLayout(columnLayout); | |
TreeColumn column = new TreeColumn(tree, SWT.LEFT); | |
columnLayout.setColumnData(column, new ColumnWeightData(100, 10)); | |
return classHierarchyTree; | |
} | |
private TableViewer createTable(Composite parent) | |
{ | |
Composite composite = new Composite(parent, SWT.NONE); | |
TableColumnLayout columnLayout = new TableColumnLayout(); | |
composite.setLayout(columnLayout); | |
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(composite); | |
final TableViewer viewer = new TableViewer(composite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.MULTI); | |
Table table = viewer.getTable(); | |
AccessibleCompositeAdapter.access(table); | |
viewer.setContentProvider(new FieldsContentProvider()); | |
viewer.setLabelProvider(new FieldsLabelProvider(this, table.getFont())); | |
TableColumn tableColumn = new TableColumn(table, SWT.LEFT); | |
tableColumn.setText(Messages.InspectorView_Type); | |
tableColumn.setWidth(50); | |
columnLayout.setColumnData(tableColumn, new ColumnWeightData(10, 50, false)); | |
tableColumn = new TableColumn(table, SWT.LEFT); | |
tableColumn.setWidth(80); | |
tableColumn.setText(Messages.InspectorView_Name); | |
columnLayout.setColumnData(tableColumn, new ColumnWeightData(30, 80)); | |
tableColumn = new TableColumn(table, SWT.LEFT); | |
tableColumn.setWidth(250); | |
tableColumn.setText(Messages.InspectorView_Value); | |
columnLayout.setColumnData(tableColumn, new ColumnWeightData(60, 250, true)); | |
table.setHeaderVisible(true); | |
return viewer; | |
} | |
private void hookContextMenu() | |
{ | |
createMenu(staticsTable); | |
createMenu(attributesTable); | |
createMenu(topTableViewer); | |
createMenu(classHierarchyTree); | |
} | |
private void createMenu(StructuredViewer viewer) | |
{ | |
Menu menu = new Menu(viewer.getControl()); | |
menu.addMenuListener(new MenuListener(menu, viewer)); | |
viewer.getControl().setMenu(menu); | |
contextMenus.add(menu); | |
} | |
private void fillContextMenu(PopupMenu manager, StructuredViewer viewer) | |
{ | |
IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); | |
if (editor != null) | |
{ | |
InspectorContextProvider contextProvider = new InspectorContextProvider(snapshot); | |
final IContextObject firstElement = contextProvider.getContext(selection.getFirstElement()); | |
IStructuredSelection editorSelection = (IStructuredSelection) editor.getSelection(); | |
final Object editorElement = editorSelection.getFirstElement(); | |
boolean isObject = firstElement != null | |
&& editorElement instanceof IContextObject | |
&& firstElement.getObjectId() != ((IContextObject) editorElement) | |
.getObjectId(); | |
if (isObject) | |
{ | |
manager.add(new Action(Messages.InspectorView_GoInto) | |
{ | |
@Override | |
public void run() | |
{ | |
updateOnSelection(new StructuredSelection(firstElement)); | |
} | |
}); | |
manager.addSeparator(); | |
} | |
QueryContextMenu contextMenu = new QueryContextMenu(editor, contextProvider); | |
contextMenu.addContextActions(manager, selection, viewer.getControl()); | |
} | |
} | |
private void showBootstrapPart() | |
{ | |
IWorkbenchPage page = getSite().getPage(); | |
if (page != null) | |
partActivated(page.getActiveEditor()); | |
} | |
protected boolean isImportant(IWorkbenchPart part) | |
{ | |
return part instanceof HeapEditor; | |
} | |
@Override | |
public void dispose() | |
{ | |
if (this.editor != null) | |
{ | |
this.editor.removeSelectionChangedListener(this); | |
this.editor = null; | |
} | |
for (Menu menu : contextMenus) | |
{ | |
if (menu != null && !menu.isDisposed()) | |
{ | |
menu.dispose(); | |
menu = null; | |
} | |
} | |
if (font != null) | |
font.dispose(); | |
getSite().getPage().removePartListener(this); | |
super.dispose(); | |
} | |
// ////////////////////////////////////////////////////////////// | |
// view life-cycle | |
// ////////////////////////////////////////////////////////////// | |
@Override | |
public void setFocus() | |
{ | |
tabFolder.getSelection().getControl().setFocus(); | |
} | |
public void partActivated(IWorkbenchPart part) | |
{ | |
if (!isImportant(part)) { return; } | |
if (!keepInSync) | |
return; | |
HeapEditor heapEditor = (HeapEditor) part; | |
if (this.editor != heapEditor) | |
{ | |
if (this.editor != null) | |
{ | |
this.editor.removeSelectionChangedListener(this); | |
} | |
this.editor = heapEditor; | |
final ISnapshotEditorInput input = heapEditor.getSnapshotInput(); | |
if (input.hasSnapshot()) | |
{ | |
this.snapshot = input.getSnapshot(); | |
} | |
else | |
{ | |
this.snapshot = null; | |
// snapshot is not yet available -> register to be informed once | |
// the snapshot has been fully loaded | |
input.addChangeListener(new ISnapshotEditorInput.IChangeListener() | |
{ | |
public void onBaselineLoaded(ISnapshot snapshot) | |
{} | |
public void onSnapshotLoaded(ISnapshot snapshot) | |
{ | |
if (InspectorView.this.snapshot == null) | |
InspectorView.this.snapshot = snapshot; | |
input.removeChangeListener(this); | |
} | |
}); | |
} | |
this.editor.addSelectionChangedListener(this); | |
updateOnSelection(this.editor.getSelection()); | |
} | |
} | |
public void partBroughtToTop(IWorkbenchPart part) | |
{ | |
partActivated(part); | |
} | |
public void partClosed(IWorkbenchPart part) | |
{ | |
if (!isImportant(part)) { return; } | |
HeapEditor heapEditor = (HeapEditor) part; | |
if (this.editor == heapEditor) | |
{ | |
this.editor.removeSelectionChangedListener(this); | |
clearInput(); | |
this.snapshot = null; | |
this.editor = null; | |
if (!keepInSync) | |
{ | |
keepInSync = true; | |
showBootstrapPart(); | |
} | |
} | |
} | |
public void partDeactivated(IWorkbenchPart part) | |
{} | |
public void partOpened(IWorkbenchPart part) | |
{} | |
public void selectionChanged(SelectionChangedEvent event) | |
{ | |
if (keepInSync) | |
{ | |
ISelection selection = event.getSelection(); | |
updateOnSelection(selection); | |
} | |
} | |
private void updateOnSelection(ISelection selection) | |
{ | |
IContextObject objectSet = null; | |
if (selection instanceof IStructuredSelection) | |
{ | |
Object object = ((IStructuredSelection) selection).getFirstElement(); | |
if (object instanceof IContextObject) | |
objectSet = (IContextObject) object; | |
} | |
long addr = 0; | |
if (objectSet instanceof IContextObjectSet) | |
{ | |
String oql = ((IContextObjectSet)objectSet).getOQL(); | |
if (oql != null && ((IContextObjectSet)objectSet).getObjectIds().length == 0) | |
{ | |
String addr0 = OQL.forAddress(0); | |
addr0 = addr0.substring(0, addr0.length() - 1); | |
if (oql.startsWith(addr0.substring(0, addr0.length())) && oql.substring(addr0.length() - 2).matches("0[xX][0-9a-fA-F]+")) //$NON-NLS-1$ | |
{ | |
addr = Long.parseUnsignedLong(oql.substring(addr0.length()), 16); | |
} | |
} | |
} | |
if (objectSet == null || (objectSet.getObjectId() < 0 && addr == 0)) | |
{ | |
clearInput(); | |
} | |
else | |
{ | |
final int objectId = objectSet.getObjectId(); | |
// do not update if the selection has not changed (double click) | |
Object data = topTableViewer.getData("input"); //$NON-NLS-1$ | |
if (data != null) | |
{ | |
int current = ((Integer) data).intValue(); | |
if (current == objectId) | |
{ | |
Object addr0 = topTableViewer.getData("address"); //$NON-NLS-1$ | |
if (addr0 != null) | |
{ | |
long currentAddr = ((Long) addr0).longValue(); | |
if (currentAddr == addr) | |
return; | |
} | |
} | |
} | |
final long objectAddress = addr; | |
final ISnapshot savedSnapshot = snapshot; | |
Job job = new Job(Messages.InspectorView_UpdateObjectDetails) | |
{ | |
@Override | |
protected IStatus run(IProgressMonitor monitor) | |
{ | |
try | |
{ | |
if (snapshot == null || savedSnapshot != snapshot) | |
return Status.OK_STATUS; | |
final IObject object; | |
if (objectId >= 0) | |
object = savedSnapshot.getObject(objectId); | |
else | |
object = (new ObjectReference(savedSnapshot, objectAddress)).getObject(); | |
// prepare object info | |
final List<Object> classInfos = prepareClassInfo(object); | |
// prepare static fields info | |
final LazyFields<?> staticFields = prepareStaticFields(object); | |
// prepare attributes | |
final LazyFields<?> attributeFields = prepareAttributes(object); | |
// update visual viewer | |
final Object toShow = prepareVisualInfo(object); | |
topTableViewer.getControl().getDisplay().asyncExec(new Runnable() | |
{ | |
public void run() | |
{ | |
topTableViewer.setInput(classInfos); | |
topTableViewer.setData("input", objectId);//$NON-NLS-1$ | |
topTableViewer.setData("address", object.getObjectAddress());//$NON-NLS-1$ | |
staticsTable.setInput(staticFields); | |
attributesTable.setInput(attributeFields); | |
updateVisualViewer(toShow); | |
IClass input = object instanceof IClass ? (IClass) object : object.getClazz(); | |
try | |
{ | |
classHierarchyTree.getTree().setRedraw(false); | |
classHierarchyTree.setInput(null); | |
classHierarchyTree | |
.setLabelProvider(new HierarchyLabelProvider(input.getObjectId())); | |
classHierarchyTree.setInput(new IClass[] { input }); | |
classHierarchyTree.expandAll(); | |
} | |
finally | |
{ | |
classHierarchyTree.getTree().setRedraw(true); | |
} | |
String txt = object.getClassSpecificName(); | |
resolvedValue.setText(txt != null ? txt : "");//$NON-NLS-1$ | |
if (!pinSelection)// no tab pinned | |
{ | |
int selectionIndex = tabFolder.getSelectionIndex(); | |
if (selectionIndex <= 1) | |
{ | |
int newSelectionIndex = (object instanceof IClass) ? 0 : 1; | |
if (selectionIndex != newSelectionIndex) | |
tabFolder.setSelection(newSelectionIndex); | |
} | |
} | |
} | |
private void updateVisualViewer(Object toShow) | |
{ | |
Object previous = visualViewer.getData("toShow");//$NON-NLS-1$ | |
if (previous instanceof Image) | |
((Image) previous).dispose(); | |
if (toShow != null) | |
{ | |
if (toShow instanceof ImageData) | |
{ | |
Image image = new Image(visualViewer.getDisplay(), (ImageData) toShow); | |
visualViewer.setData("toShow", image);//$NON-NLS-1$ | |
} | |
else if (toShow instanceof RGB) | |
{ | |
visualViewer.setData("toShow", toShow);//$NON-NLS-1$ | |
} | |
visualViewer.redraw(); | |
} | |
visualViewer.setVisible(toShow != null); | |
visualViewer.getParent().layout(); | |
} | |
}); | |
return Status.OK_STATUS; | |
} | |
catch (SnapshotException e) | |
{ | |
return new Status(IStatus.ERROR, MemoryAnalyserPlugin.PLUGIN_ID, | |
Messages.InspectorView_ErrorUpdatingInspector, e); | |
} | |
} | |
private Object prepareVisualInfo(IObject object) throws SnapshotException | |
{ | |
String kind = object.getClazz().getName(); | |
if ("org.eclipse.swt.graphics.RGB".equals(kind))//$NON-NLS-1$ | |
{ | |
Integer red = (Integer) object.resolveValue("red");//$NON-NLS-1$ | |
Integer green = (Integer) object.resolveValue("green");//$NON-NLS-1$ | |
Integer blue = (Integer) object.resolveValue("blue");//$NON-NLS-1$ | |
if (red == null || green == null || blue == null) | |
return null; | |
return new RGB(red, green, blue); | |
} | |
else if ("org.eclipse.swt.graphics.ImageData".equals(kind))//$NON-NLS-1$ | |
{ | |
IPrimitiveArray data = (IPrimitiveArray) object.resolveValue("data");//$NON-NLS-1$ | |
Integer width = (Integer) object.resolveValue("width");//$NON-NLS-1$ | |
Integer height = (Integer) object.resolveValue("height");//$NON-NLS-1$ | |
Integer depth = (Integer) object.resolveValue("depth");//$NON-NLS-1$ | |
Integer scanlinePad = (Integer) object.resolveValue("scanlinePad");//$NON-NLS-1$ | |
Integer transparentPixel = (Integer) object.resolveValue("transparentPixel");//$NON-NLS-1$ | |
if (data == null || width == null || height == null || depth == null || scanlinePad == null | |
|| transparentPixel == null) | |
return null; | |
PaletteData paletteData = makePaletteData((IInstance) object.resolveValue("palette"));//$NON-NLS-1$ | |
if (paletteData == null) | |
return null; | |
byte[] dataArray = (byte[]) data.getValueArray(); | |
byte[] dataCopy = new byte[dataArray.length]; | |
System.arraycopy(dataArray, 0, dataCopy, 0, dataArray.length); | |
ImageData imageData = new ImageData(width, height, depth, paletteData, scanlinePad, dataCopy); | |
imageData.transparentPixel = transparentPixel; | |
IPrimitiveArray alphaBytes = (IPrimitiveArray) object.resolveValue("alphaData");//$NON-NLS-1$ | |
if (alphaBytes != null) | |
{ | |
byte[] alphaDataArray = (byte[]) alphaBytes.getValueArray(); | |
byte[] alphaDataCopy = new byte[alphaDataArray.length]; | |
System.arraycopy(alphaDataArray, 0, alphaDataCopy, 0, alphaDataArray.length); | |
imageData.alphaData = alphaDataCopy; | |
} | |
return imageData; | |
} | |
return null; | |
} | |
private LazyFields<?> prepareAttributes(final IObject object) | |
{ | |
LazyFields<?> fields = null; | |
if (object instanceof IInstance) | |
fields = new LazyFields.Instance((IInstance) object); | |
else if (object instanceof IPrimitiveArray) | |
fields = new LazyFields.PrimitiveArray((IPrimitiveArray) object); | |
else if (object instanceof IObjectArray) | |
fields = new LazyFields.ObjectArray((IObjectArray) object); | |
else if (object instanceof IClass) | |
fields = new LazyFields.Class((IClass) object, true, false); | |
else | |
fields = LazyFields.EMPTY; | |
return fields; | |
} | |
private LazyFields<?> prepareStaticFields(final IObject object) | |
{ | |
LazyFields<?> fields = null; | |
if (object instanceof IClass) | |
fields = new LazyFields.Class((IClass) object, false, false); | |
else if (object instanceof IInstance) | |
fields = new LazyFields.Class(object.getClazz(), false, true); | |
else | |
fields = LazyFields.EMPTY; | |
return fields; | |
} | |
private List<Object> prepareClassInfo(final IObject object) throws SnapshotException | |
{ | |
List<Object> details = new ArrayList<Object>(); | |
details.add(new InfoItem(object, MemoryAnalyserPlugin | |
.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.ID), "0x"//$NON-NLS-1$ | |
+ Long.toHexString(object.getObjectAddress()))); | |
String className = object instanceof IClass ? ((IClass) object).getName() : object.getClazz() | |
.getName(); | |
int p = className.lastIndexOf('.'); | |
if (p < 0) // primitive | |
{ | |
InfoItem item = new InfoItem(object, ImageHelper.getImageDescriptor(ImageHelper | |
.getType(object)), className); | |
details.add(item); | |
details.add(new InfoItem(MemoryAnalyserPlugin | |
.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.PACKAGE), ""));//$NON-NLS-1$ | |
} | |
else | |
{ | |
details.add(new InfoItem(object, ImageHelper.getImageDescriptor(ImageHelper | |
.getType(object)), className.substring(p + 1))); | |
details.add(new InfoItem(MemoryAnalyserPlugin | |
.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.PACKAGE), className | |
.substring(0, p))); | |
} | |
details.add(new ObjectNode(object.getClazz())); | |
IClass superClass = object instanceof IClass ? ((IClass) object).getSuperClass() : object | |
.getClazz().getSuperClass(); | |
if (superClass != null) | |
{ | |
details.add(new InfoItem(superClass, MemoryAnalyserPlugin | |
.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.SUPERCLASS), superClass | |
.getName())); | |
} | |
ISnapshot snapshot = object.getSnapshot(); | |
if (object instanceof IClass) | |
details.add(new ObjectNode(snapshot.getObject(((IClass) object).getClassLoaderId()))); | |
else | |
details.add(new ObjectNode(snapshot.getObject(object.getClazz().getClassLoaderId()))); | |
details.add(new InfoItem(MemoryAnalyserPlugin | |
.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.SIZE), MessageUtil.format( | |
Messages.InspectorView_shallowSize, object.getUsedHeapSize()))); | |
details.add(new InfoItem(MemoryAnalyserPlugin | |
.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.SIZE), MessageUtil.format( | |
Messages.InspectorView_retainedSize, object.getRetainedHeapSize()))); | |
GCRootInfo[] gc; | |
try | |
{ | |
gc = object.getGCRootInfo(); | |
} | |
catch (SnapshotException e) | |
{ | |
gc = null; | |
} | |
details.add(gc != null ? (Object) gc : new InfoItem(MemoryAnalyserPlugin | |
.getImageDescriptor(ImageHelper.Decorations.GC_ROOT), | |
Messages.InspectorView_noGCRoot)); | |
return details; | |
} | |
private PaletteData makePaletteData(IInstance palette) throws SnapshotException | |
{ | |
Boolean isDirect = (Boolean) palette.resolveValue("isDirect");//$NON-NLS-1$ | |
if (isDirect == null) | |
return null; | |
if (isDirect) | |
{ | |
Integer redMask = (Integer) palette.resolveValue("redMask");//$NON-NLS-1$ | |
Integer greenMask = (Integer) palette.resolveValue("greenMask");//$NON-NLS-1$ | |
Integer blueMask = (Integer) palette.resolveValue("blueMask");//$NON-NLS-1$ | |
if (redMask == null || greenMask == null || blueMask == null) | |
return null; | |
return new PaletteData(redMask, greenMask, blueMask); | |
} | |
else | |
{ | |
IObjectArray array = (IObjectArray) palette.resolveValue("colors");//$NON-NLS-1$ | |
if (array == null) | |
return null; | |
RGB[] rgbs = new RGB[array.getLength()]; | |
long[] refs = array.getReferenceArray(); | |
ISnapshot snapshot = palette.getSnapshot(); | |
for (int ii = 0; ii < refs.length; ii++) | |
{ | |
int id = snapshot.mapAddressToId(refs[ii]); | |
IObject obj = snapshot.getObject(id); | |
if (obj == null) | |
return null; | |
Integer red = (Integer) obj.resolveValue("red");//$NON-NLS-1$ | |
Integer green = (Integer) obj.resolveValue("green");//$NON-NLS-1$ | |
Integer blue = (Integer) obj.resolveValue("blue");//$NON-NLS-1$ | |
if (red == null || green == null || blue == null) | |
return null; | |
rgbs[ii] = new RGB(red, green, blue); | |
} | |
return new PaletteData(rgbs); | |
} | |
} | |
}; | |
job.schedule(); | |
} | |
} | |
private void clearInput() | |
{ | |
topTableViewer.getControl().getDisplay().asyncExec(new Runnable() | |
{ | |
public void run() | |
{ | |
// fix: add one (dummy) row to each table so that the | |
// ColumnViewer does not cache the last (real) row subject | |
// which in turn often has a reference to the snapshot | |
if (topTableViewer.getContentProvider() != null) | |
{ | |
topTableViewer.setInput(new Object[] { new Object() }); | |
topTableViewer.setData("input", null); //$NON-NLS-1$ | |
} | |
if (staticsTable.getContentProvider() != null) | |
{ | |
staticsTable.setInput(LazyFields.EMPTY); | |
} | |
if (attributesTable.getContentProvider() != null) | |
{ | |
attributesTable.setInput(LazyFields.EMPTY); | |
} | |
if (classHierarchyTree.getContentProvider() != null) | |
{ | |
classHierarchyTree.setInput(new Object[] { new Object() }); | |
} | |
if (!resolvedValue.isDisposed()) | |
{ | |
resolvedValue.setText(""); //$NON-NLS-1$ | |
} | |
for (Menu menu : contextMenus) | |
{ | |
if (!menu.isDisposed()) | |
disposeItems(menu); | |
} | |
} | |
}); | |
} | |
private void disposeItems(Menu menu) | |
{ | |
MenuItem[] items = menu.getItems(); | |
for (MenuItem menuItem : items) | |
{ | |
if (!menuItem.isDisposed()) | |
menuItem.dispose(); | |
} | |
} | |
} |