blob: 2c82fe20618950afb7924f0b9748ba199d65a2f8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2021 SAP AG 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 - refactor for new/import wizard
*******************************************************************************/
package org.eclipse.mat.ui.internal.acquire;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.window.DefaultToolTip;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.internal.acquire.HeapDumpProviderDescriptor;
import org.eclipse.mat.internal.acquire.VmInfoDescriptor;
import org.eclipse.mat.query.IQueryContext;
import org.eclipse.mat.query.annotations.descriptors.IAnnotatedObjectDescriptor;
import org.eclipse.mat.query.registry.AnnotatedObjectArgumentsSet;
import org.eclipse.mat.query.registry.ArgumentDescriptor;
import org.eclipse.mat.ui.Messages;
import org.eclipse.mat.ui.internal.query.arguments.ArgumentEditor;
import org.eclipse.mat.ui.internal.query.arguments.ArgumentEditor.IEditorListener;
import org.eclipse.mat.ui.internal.query.arguments.LinkEditor.Mode;
import org.eclipse.mat.ui.internal.query.arguments.TableEditorFactory;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
/**
* Handles a table of arguments - either for a particular dump or for a particular dump provider type.
*
*/
public class ProviderArgumentsTable implements IEditorListener/*, ProcessSelectionListener*/
{
private static final int MIN_EDITOR_WIDTH = 50;
private static final String ARGUMENT = Messages.ArgumentsTable_Argument;
private static final String VALUE = Messages.ArgumentsTable_Value;
private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources());
private Table table;
private Font boldFont;
private Font normalFont;
private int tableRowHeight = SWT.DEFAULT;
private IAnnotatedObjectDescriptor providerDescriptor;
private AnnotatedObjectArgumentsSet argumentSet;
private IQueryContext context;
private List<ITableListener> listeners = Collections.synchronizedList(new ArrayList<ITableListener>());
private Map<ArgumentEditor, String> errors = Collections.synchronizedMap(new HashMap<ArgumentEditor, String>());
public interface ITableListener
{
void onInputChanged();
void onValueChanged();
void onError(String message);
void onFocus(String message);
}
public ProviderArgumentsTable(Composite parent, int style/*, ProviderArgumentsWizzardPage wizzardPage*/)
{
TableColumnLayout tableColumnLayout = new TableColumnLayout();
parent.setLayout(tableColumnLayout);
table = new Table(parent, style);
Font parentFont = parent.getFont();
table.setFont(parentFont);
table.setLinesVisible(true);
table.setHeaderVisible(true);
TableColumn column = new TableColumn(table, SWT.NONE);
column.setText(ARGUMENT);
tableColumnLayout.setColumnData(column, new ColumnWeightData(0, 100));
column = new TableColumn(table, SWT.NONE);
column.setText(VALUE);
tableColumnLayout.setColumnData(column, new ColumnWeightData(100, 100));
boldFont = resourceManager.createFont(FontDescriptor.createFrom(parentFont).setStyle(SWT.BOLD));
normalFont = resourceManager.createFont(FontDescriptor.createFrom(parentFont).setStyle(SWT.NORMAL));
table.addListener(SWT.MeasureItem, new Listener()
{
public void handleEvent(Event event)
{
event.height = tableRowHeight;
}
});
new DefaultToolTip(table, ToolTip.NO_RECREATE, false) {
private ArgumentDescriptor getEntry(Event event)
{
TableItem item = table.getItem(new Point(event.x, event.y));
if (item != null && item.getData() != null)
{
return ((ArgumentEditor) item.getData()).getDescriptor();
}
return null;
}
protected String getText(Event event)
{
ArgumentDescriptor entry = getEntry(event);
if (entry != null)
{
return entry.getHelp();
}
return null;
}
protected boolean shouldCreateToolTip(Event event)
{
table.setToolTipText(""); //$NON-NLS-1$
return getEntry(event) != null && super.shouldCreateToolTip(event);
}
protected Object getToolTipArea(Event event)
{
return getEntry(event);
}
}.activate();
}
private void setTableRowHeight(int height)
{
if (height > tableRowHeight)
{
tableRowHeight = height;
table.pack();
table.getParent().pack();
}
}
public AnnotatedObjectArgumentsSet getArgumentSet()
{
return argumentSet;
}
public IAnnotatedObjectDescriptor getProviderDescriptor()
{
return providerDescriptor;
}
void createTableContent()
{
List<ArgumentDescriptor> argumentDescriptors = providerDescriptor.getArguments();
for (ArgumentDescriptor descriptor : argumentDescriptors)
{
String flag = createArgumentLabel(descriptor);
Object argumentValue = argumentSet.getArgumentValue(descriptor);
if (descriptor.isMultiple())
{
List<?> values = (List<?>) argumentValue;
if (values == null) values = (List<?>) descriptor.getDefaultValue();
if (values == null || values.isEmpty())
{
addEditorRow(descriptor, flag, null, -1);
}
else
{
Iterator<?> valueIt = values.iterator();
Object firstValue = valueIt.next();
addEditorRow(descriptor, flag, firstValue, -1);
while (valueIt.hasNext())
{
Object objValue = valueIt.next();
addEditorRow(descriptor, "..\"..", objValue, -1); //$NON-NLS-1$
}
addEditorRow(descriptor, "..\"..", null, -1); //$NON-NLS-1$
}
}
else
{
Object value = argumentValue;
if (value == null) value = descriptor.getDefaultValue();
addEditorRow(descriptor, flag, value, -1);
}
}
for (Control control : table.getChildren())
{
if (control instanceof ArgumentEditor)
((ArgumentEditor) control).addListener(this);
}
try
{
table.getChildren()[0].setFocus();
}
catch (ArrayIndexOutOfBoundsException e)
{
// $JL-EXC$
// should not happen as we assume that table should have at least
// one child.
// If by any reason the exception occurs, the focus will not be set
}
}
public void addListener(ITableListener listener)
{
this.listeners.add(listener);
}
private String createArgumentLabel(ArgumentDescriptor descriptor)
{
String flag = descriptor.getFlag();
if (flag == null) return descriptor.getName();
else return "-" + flag;//$NON-NLS-1$
}
private void addEditorRow(ArgumentDescriptor descriptor, String flag, Object value, int index)
{
TableItem item;
if (index > 0) item = new TableItem(table, SWT.NONE, index);
else item = new TableItem(table, SWT.NONE);
item.setText(flag);
setFont(descriptor, item);
TableEditor editor = createEditor();
ArgumentEditor aec = TableEditorFactory.createTableEditor(table, context, descriptor, item);
aec.setFont(item.getFont());
editor.setEditor(aec, item, 1);
item.setData(aec);
// Adjust the table height for the editor
setTableRowHeight(aec.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
// listener should be added only to the new rows, for rows with default
// values listeners are added after the table is created and filled with
// default values
if (index > 0)
{
aec.addListener(this);
// ugly: w/o pack, the table does not redraw the editors correctly
table.pack();
table.getParent().pack();
setNewTabOrder();
}
try
{
if (value != null) aec.setValue(value);
}
catch (SnapshotException e)
{
// $JL-EXC$
// leave editor empty
}
}
private void setFont(ArgumentDescriptor descriptor, TableItem item)
{
// to make the rows in the table a little higher we set the bigger font
// to the whole table.
// and in this method we return the normal size and highlight mandatory
// arguments.
if (descriptor.isMandatory())
{
// Normal font for whole row
item.setFont(normalFont);
// Bold font for first box in row
item.setFont(0, boldFont);
}
else item.setFont(normalFont);
}
private void setNewTabOrder()
{
// this method is called when the new row was inserted to reassure that
// the "Tab" button works correct.
TableItem[] items = table.getItems();
// we need to include to the TabList only those items that are editors
Control[] newTabOrder = new Control[table.getChildren().length];
for (int i = 0, j = 0; i < items.length; i++, j++)
{
if (items[i].getData() != null) newTabOrder[j] = (ArgumentEditor) items[i].getData();
else j--;
}
table.setTabList(newTabOrder);
}
private TableEditor createEditor()
{
TableEditor editor = new TableEditor(table);
editor.horizontalAlignment = SWT.LEFT;
editor.grabHorizontal = true;
editor.minimumWidth = MIN_EDITOR_WIDTH;
return editor;
}
public synchronized void onValueChanged(Object value, ArgumentDescriptor descriptor, TableItem item, ArgumentEditor argEditor)
{
int myIndex = table.indexOf(item);
// remove error message
onError(argEditor, null);
onError(null, null);
boolean isLastOne = descriptor.isMultiple()
&& (myIndex + 1 == table.getItemCount() || ((ArgumentEditor) table.getItem(myIndex + 1).getData()).getDescriptor() != descriptor);
// update lists
if (descriptor.isMultiple())
{
List<Object> values = new ArrayList<Object>();
Control[] children = table.getChildren();
for (int ii = 0; ii < children.length; ii++)
{
if (!(children[ii] instanceof ArgumentEditor)) continue;
ArgumentEditor editor = (ArgumentEditor) children[ii];
if (editor.getDescriptor() == descriptor)
{
Object v = editor.getValue();
if (v != null) values.add(v);
}
}
if (values.isEmpty()) values = null;
Object defaultValue = descriptor.getDefaultValue();
if (defaultValue == null || !defaultValue.equals(values))
{
argumentSet.setArgumentValue(descriptor, values);
}
else
{
argumentSet.removeArgumentValue(descriptor);
}
// warn a/b mandatory arguments
if (descriptor.isMandatory() && values == null) onError(argEditor, MessageUtil.format(Messages.ArgumentsTable_isMandatory, descriptor.getName()));
// insert new row at myIndex + 1
if (isLastOne && value != null) addEditorRow(descriptor, "..\"..", null, myIndex + 1);//$NON-NLS-1$
}
else
{
Object defaultValue = descriptor.getDefaultValue();
if (defaultValue == null || !defaultValue.equals(value))
{
argumentSet.setArgumentValue(descriptor, value);
}
else
{
argumentSet.removeArgumentValue(descriptor);
}
// warn a/b mandatory arguments
if (descriptor.isMandatory() && value == null) onError(argEditor, MessageUtil.format(Messages.ArgumentsTable_isMandatory, descriptor.getName()));
}
if (providerDescriptor instanceof VmInfoDescriptor)
{
VmInfoDescriptor vmd = (VmInfoDescriptor)providerDescriptor;
try {
AcquireSnapshotAction.AcquireDumpOperation.setupVmInfo(vmd.getVmInfo(), argumentSet);
} catch (SnapshotException e) {
// ignore - set later on generating the dump
}
}
// inform about value changes
fireValueChangedEvent();
}
private void fireInputChangedEvent()
{
synchronized (listeners)
{
for (ITableListener listener : listeners)
listener.onInputChanged();
}
}
private void fireValueChangedEvent()
{
synchronized (listeners)
{
for (ITableListener listener : listeners)
listener.onValueChanged();
}
}
public void onFocus(String message)
{
fireFocusChangedEvent(message);
}
public void onModeChange(Mode mode, ArgumentDescriptor descriptor)
{
// do nothing
}
public void onError(ArgumentEditor editor, String message)
{
synchronized (errors)
{
if (message == null)
{
errors.remove(editor);
// There can be stale argument editors and errors, so remove them.
// Perhaps better done on leaving a wizard page, but this works.
for (Iterator<Map.Entry<ArgumentEditor,String>>i = errors.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<ArgumentEditor,String>e = i.next();
if (e.getKey().isDisposed())
{
i.remove();
}
}
if (errors.isEmpty()) fireErrorMessageEvent(null);
else fireErrorMessageEvent(errors.values().iterator().next());
}
else
{
errors.put(editor, message);
fireErrorMessageEvent(message);
}
}
}
private void fireErrorMessageEvent(String message)
{
synchronized (listeners)
{
for (ITableListener listener : listeners)
listener.onError(message);
}
}
public void fireFocusChangedEvent(String message)
{
synchronized (listeners)
{
for (ITableListener listener : listeners)
listener.onFocus(message);
}
}
public void providerSelected(AnnotatedObjectArgumentsSet newArgumentsSet)
{
if (newArgumentsSet == null)
{
// The provider has been deselected
providerDescriptor = null;
clearTable();
argumentSet = null;
context = null;
return;
}
IAnnotatedObjectDescriptor newProviderDescriptor = newArgumentsSet.getDescriptor();
if (!newProviderDescriptor.equals(providerDescriptor))
{
// Obtain some default values in the table based on the current
// values of the provider
for (ArgumentDescriptor ad : newProviderDescriptor.getArguments())
{
try
{
Object defaultValue;
if (newProviderDescriptor instanceof HeapDumpProviderDescriptor)
{
defaultValue = ad.getField().get(((HeapDumpProviderDescriptor)newProviderDescriptor).getHeapDumpProvider());
}
else if (newProviderDescriptor instanceof VmInfoDescriptor)
{
defaultValue = ad.getField().get(((VmInfoDescriptor)newProviderDescriptor).getVmInfo());
}
else
{
// Should never happen
defaultValue = null;
}
if (ad.isArray() && defaultValue != null)
{
// internally, all multiple values have their values held as arrays
// therefore we convert the array once and for all
int size = Array.getLength(defaultValue);
List<Object> l = new ArrayList<Object>(size);
for (int ii = 0; ii < size; ii++)
{
l.add(Array.get(defaultValue, ii));
}
Object old = ad.getDefaultValue();
ad.setDefaultValue(Collections.unmodifiableList(l));
// Has the default value changed, if so then
// perhaps the user should also reselect.
if (!Objects.deepEquals(old, defaultValue))
newArgumentsSet.setArgumentValue(ad, null);
}
else
{
Object old = ad.getDefaultValue();
ad.setDefaultValue(defaultValue);
// Has the default value changed, if so then
// perhaps the user should also reselect.
if (!Objects.deepEquals(old, defaultValue))
newArgumentsSet.setArgumentValue(ad, null);
}
}
catch (IllegalAccessException e)
{}
}
providerDescriptor = newProviderDescriptor;
clearTable();
// argumentSet = new ProviderArgumentsSet(providerDescriptor);
argumentSet = newArgumentsSet;
context = new ProviderContextImpl();
createTableContent();
}
// wizzardPage.updateDescription();
fireInputChangedEvent();
}
private void clearTable()
{
table.removeAll(); // remove the table items
/* remove all created editors */
Control[] controls = table.getChildren();
for (Control control : controls)
{
if (control instanceof ArgumentEditor) control.dispose();
}
}
}