blob: bba396b97207a5917e94c13a7d87beb0095abe96 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2020 SAP AG and IBM Corporation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors: SAP AG - initial API and implementation
* Andrew Johnson - compare tables query browser
******************************************************************************/
package org.eclipse.mat.ui.compare;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.query.IQueryContext;
import org.eclipse.mat.query.IResult;
import org.eclipse.mat.query.IResultTable;
import org.eclipse.mat.query.IResultTree;
import org.eclipse.mat.query.IStructuredResult;
import org.eclipse.mat.query.refined.RefinedStructuredResult;
import org.eclipse.mat.query.registry.ArgumentDescriptor;
import org.eclipse.mat.query.registry.ArgumentSet;
import org.eclipse.mat.query.registry.QueryDescriptor;
import org.eclipse.mat.query.registry.QueryResult;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.query.SnapshotQuery;
import org.eclipse.mat.ui.MemoryAnalyserPlugin;
import org.eclipse.mat.ui.Messages;
import org.eclipse.mat.ui.QueryExecution;
import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter;
import org.eclipse.mat.ui.actions.QueryDropDownMenuAction;
import org.eclipse.mat.ui.editor.AbstractEditorPane;
import org.eclipse.mat.ui.editor.AbstractPaneJob;
import org.eclipse.mat.ui.editor.CompositeHeapEditorPane;
import org.eclipse.mat.ui.editor.MultiPaneEditor;
import org.eclipse.mat.ui.internal.panes.QueryResultPane;
import org.eclipse.mat.ui.internal.panes.TableResultPane;
import org.eclipse.mat.ui.snapshot.panes.HistogramPane;
import org.eclipse.mat.ui.snapshot.panes.OQLPane;
import org.eclipse.mat.ui.util.ErrorHelper;
import org.eclipse.mat.ui.util.IPolicy;
import org.eclipse.mat.ui.util.NavigatorState.IStateChangeListener;
import org.eclipse.mat.ui.util.PaneState;
import org.eclipse.mat.ui.util.PopupMenu;
import org.eclipse.mat.ui.util.ProgressMonitorWrapper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.part.ViewPart;
public class CompareBasketView extends ViewPart
{
public static final String ID = "org.eclipse.mat.ui.views.CompareBasketView"; //$NON-NLS-1$
private Table table;
private TableViewer tableViewer;
private Action compareAction;
private Action clearAction;
private MoveAction moveUpAction;
private MoveAction moveDownAction;
private Action removeAction;
private Set<MultiPaneEditor> editors = new HashSet<MultiPaneEditor>();
List<ComparedResult> results = new ArrayList<ComparedResult>();
IStateChangeListener stateListener = new StateChangeListener();
IPartListener2 partListener = new PartListener();
@Override
public void createPartControl(Composite parent)
{
createTable(parent);
addToolbar();
hookContextMenu();
}
private void createTable(Composite parent)
{
tableViewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
this.table = tableViewer.getTable();
AccessibleCompositeAdapter.access(table);
TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.LEFT);
TableColumn tableColumn = column.getColumn();
tableColumn.setText(Messages.CompareBasketView_ResultsToBeComparedColumnHeader);
tableColumn.setWidth(200);
TableViewerColumn heapDumpColumn = new TableViewerColumn(tableViewer, SWT.LEFT);
tableColumn = heapDumpColumn.getColumn();
tableColumn.setText(Messages.CompareBasketView_HeapDumpColumnHeader);
tableColumn.setWidth(400);
table.setHeaderVisible(true);
table.setLinesVisible(true);
tableViewer.setContentProvider(new CompareContentProvider());
tableViewer.setLabelProvider(new CompareLabelProvider());
tableViewer.setInput(results);
}
private void addToolbar()
{
IToolBarManager manager = getViewSite().getActionBars().getToolBarManager();
moveDownAction = new MoveAction(SWT.DOWN);
manager.add(moveDownAction);
moveUpAction = new MoveAction(SWT.UP);
manager.add(moveUpAction);
manager.add(new Separator());
removeAction = new RemoveAction();
manager.add(removeAction);
clearAction = new RemoveAllAction();
manager.add(clearAction);
manager.add(new Separator());
compareAction = new CompareAction();
manager.add(compareAction);
}
@Override
public void setFocus()
{
// TODO Auto-generated method stub
}
public void addResultToCompare(PaneState state, MultiPaneEditor editor)
{
AbstractEditorPane pane = resultPane(state, editor);
ComparedResult entry = null;
if (pane != null)
{
RefinedStructuredResult rsr = pane.getAdapter(RefinedStructuredResult.class);
if (rsr != null && rsr instanceof IResultTree)
entry = new ComparedResult(state, editor, (IResultTree)rsr);
else if (rsr != null && rsr instanceof IResultTable)
entry = new ComparedResult(state, editor, (IResultTable)rsr);
else
{
QueryResult qr = pane.getAdapter(QueryResult.class);
if (qr != null && qr.getSubject() instanceof IResultTree)
entry = new ComparedResult(state, editor, (IResultTree)qr.getSubject());
else if (qr != null && qr.getSubject() instanceof IResultTable)
entry = new ComparedResult(state, editor, (IResultTable)qr.getSubject());
else if (pane instanceof HistogramPane)
{
entry = new ComparedResult(state, editor, ((HistogramPane) pane).getHistogram());
}
else if (pane instanceof TableResultPane)
{
entry = new ComparedResult(state, editor, (IResultTable) ((TableResultPane) pane).getSrcQueryResult().getSubject());
}
else if (pane instanceof QueryResultPane)
{
entry = new ComparedResult(state, editor, (IResultTree) ((QueryResultPane) pane).getSrcQueryResult().getSubject());
}
}
}
if (entry != null)
{
results.add(entry);
}
tableViewer.refresh();
clearAction.setEnabled(true);
if (results.size() > 1) compareAction.setEnabled(true);
// is the result from a unknown editor => add some cleanup actions
if (editors.add(editor))
{
// listen if the editor gets closed
editor.getSite().getPage().addPartListener(partListener);
// listen if some result from the editor gets closed
editor.getNavigatorState().addChangeStateListener(stateListener);
}
}
public static boolean accepts(PaneState state, MultiPaneEditor editor)
{
AbstractEditorPane pane = resultPane(state, editor);
if (pane != null)
{
QueryResult qr = pane.getAdapter(QueryResult.class);
if (qr != null && qr.getSubject() instanceof IResultTree)
return true;
if (qr != null && qr.getSubject() instanceof IResultTable)
return true;
}
return (pane instanceof HistogramPane) || (pane instanceof TableResultPane) ||
pane instanceof QueryResultPane &&
((QueryResultPane) pane).getSrcQueryResult().getSubject() instanceof IResultTree;
}
private static AbstractEditorPane resultPane(PaneState state, MultiPaneEditor editor)
{
AbstractEditorPane pane;
if (state.isActive() && state.getType() == PaneState.PaneType.COMPOSITE_CHILD)
{
PaneState pstate = state.getParentPaneState();
pane = editor.getEditor(pstate);
if (pane instanceof OQLPane)
pane = ((OQLPane)pane).getEmbeddedPane();
else if (pane instanceof CompositeHeapEditorPane)
{
pane = ((CompositeHeapEditorPane)pane);
}
}
else
{
pane = editor.getEditor(state);
}
return pane;
}
private void hookContextMenu()
{
MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager)
{
editorContextMenuAboutToShow(manager);
}
});
Menu menu = menuMgr.createContextMenu(table);
this.table.setMenu(menu);
menu.addMenuListener(new MenuAdapter()
{
@Override
public void menuShown(MenuEvent e)
{
TableItem[] items = table.getSelection();
final List<IStructuredResult> tables = new ArrayList<IStructuredResult>(items.length);
final List<IQueryContext> contexts = new ArrayList<IQueryContext>(items.length);
for (TableItem item : items)
{
ComparedResult result = (ComparedResult) item.getData();
tables.add(result.table);
contexts.add(result.editor.getQueryContext());
}
MultiPaneEditor editor = getEditor();
if (editor == null)
return;
QueryDropDownMenuAction qa = new QueryDropDownMenuAction(editor, new ComparePolicy(tables, contexts, editor.getQueryContext()), false);
PopupMenu menu1 = new PopupMenu();
qa.contribute(menu1);
IStatusLineManager statusLineManager = getViewSite().getActionBars().getStatusLineManager();
menu1.addToMenu(statusLineManager, table.getMenu());
}
});
}
private void editorContextMenuAboutToShow(IMenuManager manager)
{
TableItem[] selection = table.getSelection();
if (selection.length == 0) return;
manager.add(moveDownAction);
manager.add(moveUpAction);
manager.add(removeAction);
}
@Override
public void dispose()
{
for (MultiPaneEditor editor : editors)
{
if (!editor.isDisposed())
{
// remove listener for if the editor gets closed
editor.getSite().getPage().removePartListener(partListener);
// remove listener for if some result from the editor gets closed
editor.getNavigatorState().removeChangeStateListener(stateListener);
}
}
}
private class PartListener implements IPartListener2
{
public void partVisible(IWorkbenchPartReference partRef)
{}
public void partOpened(IWorkbenchPartReference partRef)
{}
public void partInputChanged(IWorkbenchPartReference partRef)
{}
public void partHidden(IWorkbenchPartReference partRef)
{}
public void partDeactivated(IWorkbenchPartReference partRef)
{}
public void partBroughtToTop(IWorkbenchPartReference partRef)
{}
public void partActivated(IWorkbenchPartReference partRef)
{}
public void partClosed(IWorkbenchPartReference partRef)
{
IWorkbenchPart part = partRef.getPart(false);
List<ComparedResult> toBeRemoved = new ArrayList<ComparedResult>();
for (ComparedResult res : results)
{
if (res.editor == part)
toBeRemoved.add(res);
}
editors.remove(part);
results.removeAll(toBeRemoved);
if (!table.isDisposed())
{
tableViewer.refresh();
}
updateButtons();
}
}
private class StateChangeListener implements IStateChangeListener
{
public void onStateChanged(PaneState state)
{
if (state != null && !state.isActive())
{
List<ComparedResult> toBeRemoved = new ArrayList<ComparedResult>();
for (ComparedResult res : results)
{
if (res.state == state)
toBeRemoved.add(res);
}
results.removeAll(toBeRemoved);
tableViewer.refresh();
updateButtons();
}
}
}
static class ComparedResult
{
PaneState state;
MultiPaneEditor editor;
IStructuredResult table;
public ComparedResult(PaneState state, MultiPaneEditor editor, IStructuredResult table)
{
super();
this.state = state;
this.editor = editor;
this.table = table;
}
}
static class CompareLabelProvider extends LabelProvider implements ITableLabelProvider
{
public Image getColumnImage(Object element, int columnIndex)
{
ComparedResult res = (ComparedResult) element;
return (columnIndex == 0) ? res.state.getImage() : null;
}
public String getColumnText(Object element, int columnIndex)
{
ComparedResult res = (ComparedResult) element;
switch (columnIndex)
{
case 0:
return res.state.getIdentifier();
case 1:
return res.editor.getResourceFile().getAbsolutePath();
default:
return null;
}
}
}
static class CompareContentProvider implements IStructuredContentProvider
{
public Object[] getElements(Object inputElement)
{
@SuppressWarnings("unchecked")
List<ComparedResult> results = (List<ComparedResult>) inputElement;
return results.toArray();
}
public void dispose()
{}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput)
{
// TODO Auto-generated method stub
}
}
class CompareAction extends Action
{
public CompareAction()
{
setText(Messages.CompareBasketView_CompareButtonLabel);
setToolTipText(Messages.CompareBasketView_CompareTooltip);
setImageDescriptor(MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.EXECUTE_QUERY));
setEnabled(false);
}
@Override
public void run()
{
try
{
final MultiPaneEditor editor = getEditor();
final List<IStructuredResult> tables = new ArrayList<IStructuredResult>(results.size());
final List<ISnapshot> snapshots = new ArrayList<ISnapshot>(results.size());
for (int i = 0; i < results.size(); i++)
{
tables.add(results.get(i).table);
snapshots.add((ISnapshot)results.get(i).editor.getQueryContext().get(ISnapshot.class, null));
}
String query = "comparetablesquery"; //$NON-NLS-1$
final SnapshotQuery compareQuery = SnapshotQuery.lookup(query,
(ISnapshot) editor.getQueryContext().get(ISnapshot.class, null));
compareQuery.setArgument("tables", tables); //$NON-NLS-1$
compareQuery.setArgument("snapshots", snapshots); //$NON-NLS-1$
// Asynchronous as may take a while
Job job = new AbstractPaneJob(compareQuery.getDescriptor().getIdentifier(), null)
{
@Override
protected IStatus doRun(IProgressMonitor monitor)
{
try
{
ProgressMonitorWrapper listener = new ProgressMonitorWrapper(monitor);
IResult absolute = compareQuery.execute(listener);
QueryResult queryResult = new QueryResult(null, Messages.CompareBasketView_ComparedTablesResultTitle,
absolute);
QueryExecution.displayResult(editor, null, null, queryResult, false);
return Status.OK_STATUS;
}
catch (SnapshotException e)
{
return ErrorHelper.createErrorStatus(e);
}
}
};
job.setUser(true);
job.schedule();
}
catch (Exception e)
{
ErrorHelper.logThrowable(e);
}
}
}
class RemoveAllAction extends Action
{
public RemoveAllAction()
{
setText(Messages.CompareBasketView_ClearButtonLabel);
setToolTipText(Messages.CompareBasketView_ClearTooltip);
setImageDescriptor(MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.REMOVE_ALL));
setEnabled(false);
}
@Override
public void run()
{
results.clear();
tableViewer.refresh();
setEnabled(false);
compareAction.setEnabled(false);
}
}
class RemoveAction extends Action implements ISelectionChangedListener
{
public RemoveAction()
{
setText(Messages.CompareBasketView_RemoveButtonLabel);
setToolTipText(Messages.CompareBasketView_RemoveTooltip);
setImageDescriptor(MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.REMOVE));
setEnabled(false);
tableViewer.addSelectionChangedListener(this);
}
@Override
public void run()
{
int[] selectionIndeces = tableViewer.getTable().getSelectionIndices();
int alreadyRemoved = 0;
for (int idx : selectionIndeces)
{
results.remove(idx - alreadyRemoved);
alreadyRemoved++;
}
tableViewer.refresh();
setEnabled(false);
if (results.size() < 2) compareAction.setEnabled(false);
}
public void selectionChanged(SelectionChangedEvent event)
{
setEnabled(!tableViewer.getSelection().isEmpty());
}
}
class MoveAction extends Action implements ISelectionChangedListener
{
private int direction;
public MoveAction(int direction)
{
this.direction = direction;
if (direction == SWT.UP)
{
setText(Messages.CompareBasketView_MoveUpButtonLabel);
setToolTipText(Messages.CompareBasketView_MoveUpTooltip);
setImageDescriptor(MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.MOVE_UP));
}
else if (direction == SWT.DOWN)
{
setText(Messages.CompareBasketView_MoveDownButtonLabel);
setToolTipText(Messages.CompareBasketView_MoveDownTooltip);
setImageDescriptor(MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.MOVE_DOWN));
}
setEnabled(false);
tableViewer.addSelectionChangedListener(this);
}
@Override
public void run()
{
int idx = tableViewer.getTable().getSelectionIndex();
ComparedResult selectedResult = (ComparedResult) ((IStructuredSelection) tableViewer.getSelection()).getFirstElement();
if (direction == SWT.UP)
{
results.set(idx, results.get(idx - 1));
results.set(idx - 1, selectedResult);
}
else if (direction == SWT.DOWN)
{
results.set(idx, results.get(idx + 1));
results.set(idx + 1, selectedResult);
}
tableViewer.setInput(results);
tableViewer.getTable().setSelection(direction == SWT.UP ? idx - 1 : idx + 1);
moveUpAction.updateState();
moveDownAction.updateState();
}
public void selectionChanged(SelectionChangedEvent event)
{
if (event.getSelection() instanceof IStructuredSelection)
{
updateState();
}
else
{
setEnabled(false);
}
}
void updateState()
{
IStructuredSelection selection = (IStructuredSelection) tableViewer.getSelection();
if (selection.size() != 1)
{
setEnabled(false);
return;
}
int idx = tableViewer.getTable().getSelectionIndex();
if (idx == 0 && direction == SWT.UP) setEnabled(false);
else if (idx == results.size() - 1 && direction == SWT.DOWN) setEnabled(false);
else setEnabled(true);
}
}
private static final class ComparePolicy implements IPolicy
{
private final List<IStructuredResult> tables;
private final List<IQueryContext> contexts;
private final IQueryContext currentContext;
private final List<ISnapshot> snapshots;
private final ISnapshot currentSnapshot;
private ComparePolicy(List<IStructuredResult> tables, List<IQueryContext> contexts, IQueryContext currentContext)
{
this.tables = tables;
this.contexts = contexts;
this.currentContext = currentContext;
snapshots = new ArrayList<ISnapshot>();
for (IQueryContext ctx : contexts)
{
snapshots.add((ISnapshot)ctx.get(ISnapshot.class, null));
}
currentSnapshot = (ISnapshot)currentContext.get(ISnapshot.class, null);
}
/**
* Only operate on queries with multiple tables and query contexts
*/
public boolean accept(QueryDescriptor query)
{
boolean foundTables = false;
boolean foundContexts = false;
boolean foundSnapshots = false;
for (ArgumentDescriptor argument : query.getArguments()) {
if (IStructuredResult.class.isAssignableFrom(argument.getType()))
{
if (argument.isMultiple())
{
for (IStructuredResult res : tables)
{
if (!argument.getType().isAssignableFrom(res.getClass()))
{
if (res instanceof RefinedStructuredResult)
{
// Perhaps the query needs a specific sort of result, not the refined version
IStructuredResult isr = ((RefinedStructuredResult)res).unwrap();
if (!argument.getType().isAssignableFrom(isr.getClass()))
// Can't convert unwrapped table/tree
return false;
}
else
{
// Can't convert table/tree
return false;
}
}
foundTables = true;
}
}
}
}
for (ArgumentDescriptor argument : query.getArguments()) {
if (IQueryContext.class.isAssignableFrom(argument.getType()))
{
if (argument.isMultiple())
{
foundContexts = true;
}
}
if (ISnapshot.class.isAssignableFrom(argument.getType()))
{
if (argument.isMultiple())
{
foundSnapshots = true;
}
}
}
if (!(foundContexts || foundSnapshots))
{
foundContexts = true;
for (IQueryContext ctx : contexts)
{
if (!ctx.equals(currentContext))
{
foundContexts = false;
break;
}
}
}
if (!(foundContexts || foundSnapshots))
{
foundSnapshots = true;
for (ISnapshot snap : snapshots)
{
if (!snap.equals(currentSnapshot))
{
foundSnapshots = false;
break;
}
}
}
return foundTables && (foundContexts || foundSnapshots);
}
public void fillInObjectArguments(ISnapshot snapshot, QueryDescriptor query, ArgumentSet set)
{
for (ArgumentDescriptor argument : query.getArguments()) {
if (IStructuredResult.class.isAssignableFrom(argument.getType()))
{
List<IStructuredResult> tables1 = new ArrayList<IStructuredResult>();
// Do we need to unwrap some results?
for (IStructuredResult res : tables)
{
// Perhaps the query needs a specific sort of result, not the refined version
if (!argument.getType().isAssignableFrom(res.getClass()) && res instanceof RefinedStructuredResult)
{
// Already tested in accept() that this works
IStructuredResult isr = ((RefinedStructuredResult)res).unwrap();
tables1.add(isr);
}
else
tables1.add(res);
}
if (argument.isMultiple())
{
set.setArgumentValue(argument, tables1);
}
else
{
set.setArgumentValue(argument, tables1.get(0));
}
}
else if (IQueryContext.class.isAssignableFrom(argument.getType()))
{
/*
* Only fill in multiple contexts from the tables.
* A single is the editor context.
*/
if (argument.isMultiple())
{
set.setArgumentValue(argument, contexts);
}
}
else if (ISnapshot.class.isAssignableFrom(argument.getType()))
{
/*
* Only fill in multiple snapshots from the tables.
* A single snapshot is the editor snapshot.
*/
if (argument.isMultiple())
{
set.setArgumentValue(argument, snapshots);
}
}
}
}
}
private void updateButtons()
{
compareAction.setEnabled(results.size() > 1);
clearAction.setEnabled(results.size() > 0);
}
private MultiPaneEditor getEditor()
{
if (results.size() > 0)
{
return (MultiPaneEditor) results.get(results.size() - 1).editor.getSite().getPage().getActiveEditor();
}
return null;
}
}