/*******************************************************************************
* Copyright (c) 2012 Red Hat.
* 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:
* Red Hat - Sami Wagiaalla
* Red Hat - Andrew Ferrazzutti
*******************************************************************************/
package org.eclipse.linuxtools.internal.systemtap.ui.ide.launcher;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.debug.ui.ILaunchConfigurationTab;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.IDEPlugin;
import org.eclipse.linuxtools.systemtap.graphing.core.datasets.IDataSet;
import org.eclipse.linuxtools.systemtap.graphing.core.datasets.IDataSetParser;
import org.eclipse.linuxtools.systemtap.graphing.core.datasets.IFilteredDataSet;
import org.eclipse.linuxtools.systemtap.graphing.core.datasets.row.LineParser;
import org.eclipse.linuxtools.systemtap.graphing.core.datasets.row.RowDataSet;
import org.eclipse.linuxtools.systemtap.graphing.core.structures.GraphData;
import org.eclipse.linuxtools.systemtap.graphing.ui.widgets.ExceptionErrorDialog;
import org.eclipse.linuxtools.systemtap.graphing.ui.wizards.dataset.DataSetFactory;
import org.eclipse.linuxtools.systemtap.graphing.ui.wizards.graph.GraphFactory;
import org.eclipse.linuxtools.systemtap.graphing.ui.wizards.graph.SelectGraphAndSeriesWizard;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.plugin.AbstractUIPlugin;
public class SystemTapScriptGraphOptionsTab extends
AbstractLaunchConfigurationTab {
/**
* The maximum number of regular expressions that can be stored in a configuration.
*/
static final int MAX_NUMBER_OF_REGEXS = 20;
/**
* The maximum length of an output-parsing regular expression.
*/
static final int MAX_REGEX_LENGTH = 200;
// Note: any non-private String key with a trailing underscore is to be appended with an integer when looking up values.
static final String RUN_WITH_CHART = "runWithChart"; //$NON-NLS-1$
static final String NUMBER_OF_REGEXS = "numberOfRegexs"; //$NON-NLS-1$
static final String NUMBER_OF_COLUMNS = "numberOfColumns_"; //$NON-NLS-1$
static final String REGEX_BOX = "regexBox_"; //$NON-NLS-1$
static final String NUMBER_OF_EXTRAS = "numberOfExtras_"; //$NON-NLS-1$
static final String EXTRA_BOX = "extraBox_"; //$NON-NLS-1$
static final String REGULAR_EXPRESSION = "regularExpression_"; //$NON-NLS-1$
static final String SAMPLE_OUTPUT = "sampleOutput_"; //$NON-NLS-1$
// Note: all graph-related keys point to 2D lists (regular expression & graph number),
// except for GRAPH_Y_SERIES (which is a 3D list).
private static final String NUMBER_OF_GRAPHS = "numberOfGraphs"; //$NON-NLS-1$
private static final String GRAPH_TITLE = "graphTitle"; //$NON-NLS-1$
private static final String GRAPH_KEY = "graphKey"; //$NON-NLS-1$
private static final String GRAPH_X_SERIES = "graphXSeries"; //$NON-NLS-1$
private static final String GRAPH_ID = "graphID"; //$NON-NLS-1$
private static final String GRAPH_Y_SERIES_LENGTH = "graphYSeriesLength"; //$NON-NLS-1$
private static final String GRAPH_Y_SERIES = "graphYSeries"; //$NON-NLS-1$
protected Pattern pattern;
protected Matcher matcher;
private Combo regularExpressionCombo;
private Button removeRegexButton;
private Text sampleOutputText;
private Composite textFieldsComposite;
/**
* This value controls whether or not the ModifyListeners associated with
* the Texts will perform when dispatched. Sometimes the listeners should
* be disabled to prevent needless/unsafe operations.
*/
private boolean textListenersEnabled = true;
private Group outputParsingGroup;
private Button runWithChartCheckButton;
private Table graphsTable;
private Button addGraphButton, duplicateGraphButton, editGraphButton, removeGraphButton;
private TableItem selectedTableItem;
private Group graphsGroup;
private int numberOfVisibleColumns = 0;
private boolean graphingEnabled = true;
/**
* A list of error messages, each entry corresponding to an entered regular expression.
*/
private List<String> regexErrorMessages = new ArrayList<>();
/**
* The index of the selected regular expression.
*/
private int selectedRegex = -1;
/**
* A list containing the user-defined sample outputs associated with the regex of every index.
*/
private List<String> outputList = new ArrayList<>();
/**
* A name is given to each group captured by a regular expression. This stack contains
* the names of all of a regex's groups that have been deleted, so each name may be
* restored (without having to retype it) when a group is added again.
*/
private Stack<String> cachedNames = new Stack<>();
/**
* A list of cachedNames stacks, containing one entry for each regular expression stored.
*/
private List<Stack<String>> cachedNamesList = new ArrayList<>();
/**
* A two-dimensional list that holds references to the names given to each regular expression's captured groups.
*/
private List<List<String>> columnNamesList = new ArrayList<>();
/**
* A list holding the data of every graph for the selected regular expression.
*/
private List<GraphData> graphsData = new LinkedList<>();
/**
* A list of graphsData lists. This is needed because each regular expression has its own set of graphs.
*/
private List<LinkedList<GraphData>> graphsDataList = new ArrayList<>();
/**
* A list of GraphDatas that rely on series information that has been deleted from their relying regex.
*/
private List<GraphData> badGraphs = new LinkedList<>();
private ModifyListener regexListener = event -> {
if (!textListenersEnabled || regularExpressionCombo.getSelectionIndex() != -1) {
return;
}
regularExpressionCombo.setItem(selectedRegex, regularExpressionCombo.getText());
regularExpressionCombo.select(selectedRegex);
refreshRegexRows();
updateLaunchConfigurationDialog();
};
private ModifyListener sampleOutputListener = event -> {
if (!textListenersEnabled) {
return;
}
outputList.set(selectedRegex, sampleOutputText.getText());
refreshRegexRows();
updateLaunchConfigurationDialog();
};
private ModifyListener columnNameListener = event -> {
if (!textListenersEnabled) {
return;
}
ArrayList<String> columnNames = new ArrayList<>();
Control[] children = textFieldsComposite.getChildren();
for (int i = 0; i < numberOfVisibleColumns; i++) {
columnNames.add(((Text)children[i*4 + 2]).getText());
}
columnNamesList.set(selectedRegex, columnNames);
updateLaunchConfigurationDialog();
};
private SelectionAdapter regexGenerator = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
MessageDialog dialog;
IWorkbench workbench = PlatformUI.getWorkbench();
IPath scriptPath = null;
for (ILaunchConfigurationTab tab : getLaunchConfigurationDialog().getTabs()) {
if (tab instanceof SystemTapScriptLaunchConfigurationTab) {
scriptPath = ((SystemTapScriptLaunchConfigurationTab) tab).getScriptPath();
break;
}
}
if (scriptPath == null) {
dialog = new MessageDialog(workbench
.getActiveWorkbenchWindow().getShell(), Messages.SystemTapScriptGraphOptionsTab_generateFromPrintsErrorTitle, null,
Messages.SystemTapScriptGraphOptionsTab_generateFromPrintsError,
MessageDialog.ERROR, new String[]{"OK"}, 0); //$NON-NLS-1$
dialog.open();
return;
}
dialog = new MessageDialog(workbench
.getActiveWorkbenchWindow().getShell(), Messages.SystemTapScriptGraphOptionsTab_generateFromPrintsTitle, null,
Messages.SystemTapScriptGraphOptionsTab_generateFromPrintsMessage,
MessageDialog.QUESTION, new String[]{"Yes", "Cancel"}, 0); //$NON-NLS-1$ //$NON-NLS-2$
int result = dialog.open();
if (result != 0) { // Cancel
return;
}
List<Entry<String, Integer>> regexs = SystemTapRegexGenerator.generateFromPrintf(scriptPath, MAX_NUMBER_OF_REGEXS);
if (regexs.size() == 0) {
dialog = new MessageDialog(workbench
.getActiveWorkbenchWindow().getShell(), Messages.SystemTapScriptGraphOptionsTab_generateFromPrintsErrorTitle, null,
Messages.SystemTapScriptGraphOptionsTab_generateFromPrintsEmpty,
MessageDialog.ERROR, new String[]{"OK"}, 0); //$NON-NLS-1$
dialog.open();
} else {
// Since script output has been found, reset the configuration's regexs.
textListenersEnabled = false;
regularExpressionCombo.removeAll();
outputList.clear();
regexErrorMessages.clear();
columnNamesList.clear();
cachedNamesList.clear();
graphsTable.removeAll();
graphsDataList.clear();
badGraphs.clear();
for (int i = 0, n = regexs.size(); i < n; i++) {
List<String> columnNames = new ArrayList<>();
for (int c = 0, numColumns = regexs.get(i).getValue(); c < numColumns; c++) {
columnNames.add(MessageFormat.format(Messages.SystemTapScriptGraphOptionsTab_defaultColumnTitleBase, c+1));
}
regularExpressionCombo.add(regexs.get(i).getKey());
outputList.add(""); //$NON-NLS-1$ //For empty "sample output" entry.
regexErrorMessages.add(null);
columnNamesList.add(columnNames);
cachedNamesList.add(new Stack<String>());
graphsDataList.add(new LinkedList<GraphData>());
}
if (getNumberOfRegexs() < MAX_NUMBER_OF_REGEXS) {
regularExpressionCombo.add(Messages.SystemTapScriptGraphOptionsTab_regexAddNew);
}
textListenersEnabled = true;
removeRegexButton.setEnabled(getNumberOfRegexs() > 1);
regularExpressionCombo.select(0);
updateRegexSelection(0, true);
checkAllOtherErrors(); // Check for errors in case there was a problem with regex generation
updateLaunchConfigurationDialog();
}
}
};
/**
* Returns the list of the names given to reach regular expression.
* @param configuration
* @return
*/
public static List<String> createDatasetNames(ILaunchConfiguration configuration) {
try {
int numberOfRegexs = configuration.getAttribute(NUMBER_OF_REGEXS, 0);
ArrayList<String> names = new ArrayList<>(numberOfRegexs);
for (int r = 0; r < numberOfRegexs; r++) {
names.add(MessageFormat.format(Messages.SystemTapScriptGraphOptionsTab_graphSetTitleBase, r + 1));
}
return names;
} catch (CoreException e) {
ExceptionErrorDialog.openError(Messages.SystemTapScriptGraphOptionsTab_cantInitializeTab, e);
}
return null;
}
/**
* Creates a list of parsers, one for each regular expression created, that will be used
* to parse the output of a running script.
* @param configuration The desired run configuration.
* @return A list of parsers.
*/
public static List<IDataSetParser> createDatasetParsers(ILaunchConfiguration configuration) {
try {
int numberOfRegexs = configuration.getAttribute(NUMBER_OF_REGEXS, 0);
ArrayList<IDataSetParser> parsers = new ArrayList<>(numberOfRegexs);
for (int r = 0; r < numberOfRegexs; r++) {
parsers.add(new LineParser("^" + configuration.getAttribute(REGULAR_EXPRESSION + r, "") + "$")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return parsers;
} catch (CoreException e) {
ExceptionErrorDialog.openError(Messages.SystemTapScriptGraphOptionsTab_cantInitializeTab, e);
}
return null;
}
/**
* Creates a data set corresponding to the titles given to each output column
* from each of a run configuration's regular expressions.
* @param configuration
* @return
*/
public static List<IFilteredDataSet> createDataset(ILaunchConfiguration configuration) {
try {
int numberOfRegexs = configuration.getAttribute(NUMBER_OF_REGEXS, 0);
ArrayList<IFilteredDataSet> datasets = new ArrayList<>(numberOfRegexs);
for (int r = 0; r < numberOfRegexs; r++) {
int numberOfColumns = configuration.getAttribute(NUMBER_OF_COLUMNS + r, 0);
ArrayList<String> labels = new ArrayList<>(numberOfColumns);
for (int c = 0; c < numberOfColumns; c++) {
labels.add(configuration.getAttribute(get2DConfigData(REGEX_BOX, r, c), "")); //$NON-NLS-1$
}
datasets.add(DataSetFactory.createFilteredDataSet(RowDataSet.ID, labels.toArray(new String[] {})));
}
return datasets;
} catch (CoreException e) {
ExceptionErrorDialog.openError(Messages.SystemTapScriptGraphOptionsTab_cantInitializeTab, e);
}
return null;
}
/**
* Creates graph data corresponding to the graphs that will plot a script's parsed output data.
* @param configuration The desired run configuration.
* @return A data set.
*/
public static List<LinkedList<GraphData>> createGraphsFromConfiguration (ILaunchConfiguration configuration)
throws CoreException {
// Restrict number of regexs to at least one, so at least
// one inner list will exist in the return value.
int numberOfRegexs = Math.max(configuration.getAttribute(NUMBER_OF_REGEXS, 1), 1);
ArrayList<LinkedList<GraphData>> graphsList = new ArrayList<>(numberOfRegexs);
for (int r = 0; r < numberOfRegexs; r++) {
int numberOfGraphs = configuration.getAttribute(NUMBER_OF_GRAPHS + r, 0);
LinkedList<GraphData> graphs = new LinkedList<>();
for (int i = 0; i < numberOfGraphs; i++) {
GraphData graphData = new GraphData();
graphData.title = configuration.getAttribute(get2DConfigData(GRAPH_TITLE, r, i), (String) null);
graphData.key = configuration.getAttribute(get2DConfigData(GRAPH_KEY, r, i), (String) null);
graphData.xSeries = configuration.getAttribute(get2DConfigData(GRAPH_X_SERIES, r, i), 0);
graphData.graphID = configuration.getAttribute(get2DConfigData(GRAPH_ID, r, i), (String) null);
int ySeriesLength = configuration.getAttribute(get2DConfigData(GRAPH_Y_SERIES_LENGTH, r, i), 0);
if (ySeriesLength == 0) {
graphData.ySeries = null;
} else {
int[] ySeries = new int[ySeriesLength];
for (int j = 0; j < ySeriesLength; j++) {
ySeries[j] = configuration.getAttribute(get2DConfigData(GRAPH_Y_SERIES, r, i + "_" + j), 0); //$NON-NLS-1$
}
graphData.ySeries = ySeries;
}
graphs.add(graphData);
}
graphsList.add(graphs);
}
return graphsList;
}
/**
* Returns the key associated with the i'th data item of the r'th regular expression.
* @param configDataName The type of data to access from the configuration.
* @param r The index of the regular expression.
* @param i The index of the data item to access.
*/
private static String get2DConfigData(String configDataName, int r, int i) {
return configDataName + r + "_" + i; //$NON-NLS-1$
}
/**
* Returns the key associated with the data item of the r'th regular expression, tagged by string s.
* @param configDataName The type of data to access from the configuration.
* @param r The index of the regular expression.
* @param s The string to put at the end of the key.
*/
private static String get2DConfigData(String configDataName, int r, String s) {
return configDataName + r + "_" + s; //$NON-NLS-1$
}
/**
* Returns the total number of regular expressions of the current configuration.
*/
private int getNumberOfRegexs() {
return outputList.size();
}
@Override
public void createControl(Composite parent) {
GridLayout layout = new GridLayout();
Composite top = new Composite(parent, SWT.NONE);
setControl(top);
top.setLayout(layout);
top.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
this.runWithChartCheckButton = new Button(top, SWT.CHECK);
runWithChartCheckButton.setText(Messages.SystemTapScriptGraphOptionsTab_graphOutputRun);
runWithChartCheckButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
setGraphingEnabled(runWithChartCheckButton.getSelection());
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
setGraphingEnabled(runWithChartCheckButton.getSelection());
}
});
runWithChartCheckButton.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_graphOutput);
this.outputParsingGroup = new Group(top, SWT.SHADOW_ETCHED_IN);
outputParsingGroup.setText(Messages.SystemTapScriptGraphOptionsTab_outputLabel);
outputParsingGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
this.createColumnSelector(outputParsingGroup);
this.graphsGroup = new Group(top, SWT.SHADOW_ETCHED_IN);
// Set the text here just to allow proper sizing.
graphsGroup.setText(MessageFormat.format(Messages.SystemTapScriptGraphOptionsTab_graphSetTitleBase, 1));
graphsGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
createGraphCreateArea(graphsGroup);
setGraphingEnabled(false);
runWithChartCheckButton.setSelection(false);
}
private void createColumnSelector(Composite parent) {
GridLayout layout = new GridLayout();
parent.setLayout(layout);
Composite topLayout = new Composite(parent, SWT.NONE);
topLayout.setLayout(new GridLayout(1, false));
topLayout.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
Button generateExpsButton = new Button(topLayout, SWT.PUSH);
generateExpsButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
generateExpsButton.setText(Messages.SystemTapScriptGraphOptionsTab_generateFromPrintsButton);
generateExpsButton.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_generateFromPrintsTooltip);
generateExpsButton.addSelectionListener(regexGenerator);
Composite regexButtonLayout = new Composite(parent, SWT.NONE);
regexButtonLayout.setLayout(new GridLayout(3, false));
regexButtonLayout.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
Label selectedRegexLabel = new Label(regexButtonLayout, SWT.NONE);
selectedRegexLabel.setText(Messages.SystemTapScriptGraphOptionsTab_regexLabel);
selectedRegexLabel.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_regexTooltip);
regularExpressionCombo = new Combo(regexButtonLayout, SWT.DROP_DOWN);
regularExpressionCombo.setTextLimit(MAX_REGEX_LENGTH);
regularExpressionCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
regularExpressionCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int selected = regularExpressionCombo.getSelectionIndex();
if (selected == selectedRegex) {
return;
}
// If deselecting an empty regular expression, delete it automatically.
if (regularExpressionCombo.getItem(selectedRegex).isEmpty()
&& graphsDataList.get(selectedRegex).size() == 0
&& outputList.get(selectedRegex).isEmpty()) {
// If the deselected regex is the last one in the combo, just quit.
// Otherwise, the deleted blank entry would be replaced by another blank entry.
if (selected == regularExpressionCombo.getItemCount() - 1) {
regularExpressionCombo.select(selectedRegex); // To keep the text blank.
return;
}
removeRegex(false);
if (selected > selectedRegex) {
selected--;
}
}
// When selecting the "Add New Regex" item in the combo (which is always the last item),
// update all appropriate values to make room for a new regular expression.
if (selected == regularExpressionCombo.getItemCount() - 1 && getNumberOfRegexs() < MAX_NUMBER_OF_REGEXS) {
outputList.add(""); //$NON-NLS-1$
regexErrorMessages.add(null);
columnNamesList.add(new ArrayList<String>());
cachedNamesList.add(new Stack<String>());
graphsDataList.add(new LinkedList<GraphData>());
// Remove "Add New Regex" from the selected combo item; make it blank.
regularExpressionCombo.setItem(selected, ""); //$NON-NLS-1$
regularExpressionCombo.select(selected);
updateRegexSelection(selected, false);
updateLaunchConfigurationDialog();
// Enable the "remove" button if only one item was present before.
// (Don't do this _every_ time something is added.)
if (getNumberOfRegexs() == 2) {
removeRegexButton.setEnabled(true);
}
if (getNumberOfRegexs() < MAX_NUMBER_OF_REGEXS) {
regularExpressionCombo.add(Messages.SystemTapScriptGraphOptionsTab_regexAddNew);
}
} else {
updateRegexSelection(selected, false);
}
}
});
regularExpressionCombo.addModifyListener(regexListener);
removeRegexButton = new Button(regexButtonLayout, SWT.PUSH);
removeRegexButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));
removeRegexButton.setText(Messages.SystemTapScriptGraphOptionsTab_regexRemove);
removeRegexButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
IWorkbench workbench = PlatformUI.getWorkbench();
MessageDialog dialog = new MessageDialog(workbench
.getActiveWorkbenchWindow().getShell(), Messages.SystemTapScriptGraphOptionsTab_removeRegexTitle, null,
MessageFormat.format(Messages.SystemTapScriptGraphOptionsTab_removeRegexAsk,
regularExpressionCombo.getItem(selectedRegex)),
MessageDialog.QUESTION, new String[]{"Yes", "No"}, 0); //$NON-NLS-1$ //$NON-NLS-2$
int result = dialog.open();
if (result == 0) { //Yes
removeRegex(true);
}
}
});
GridLayout twoColumns = new GridLayout(2, false);
Composite regexSummaryComposite = new Composite(parent, SWT.NONE);
regexSummaryComposite.setLayout(twoColumns);
regexSummaryComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Label sampleOutputLabel = new Label(regexSummaryComposite, SWT.NONE);
sampleOutputLabel.setText(Messages.SystemTapScriptGraphOptionsTab_sampleOutputLabel);
sampleOutputLabel.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_sampleOutputTooltip);
this.sampleOutputText = new Text(regexSummaryComposite, SWT.BORDER);
this.sampleOutputText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
this.sampleOutputText.addModifyListener(sampleOutputListener);
sampleOutputText.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_sampleOutputTooltip);
Composite expressionTableLabels = new Composite(parent, SWT.NONE);
expressionTableLabels.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
expressionTableLabels.setLayout(twoColumns);
Label label = new Label(expressionTableLabels, SWT.NONE);
label.setText(Messages.SystemTapScriptGraphOptionsTab_columnTitle);
label.setAlignment(SWT.LEFT);
Label label2 = new Label(expressionTableLabels, SWT.NONE);
label2.setAlignment(SWT.LEFT);
label2.setText(Messages.SystemTapScriptGraphOptionsTab_extractedValueLabel);
ScrolledComposite regexTextScrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL | SWT.BORDER);
GridData data = new GridData(SWT.FILL, SWT.FILL, true, false);
data.heightHint = 200;
regexTextScrolledComposite.setLayoutData(data);
textFieldsComposite = new Composite(regexTextScrolledComposite, SWT.NONE);
textFieldsComposite.setLayout(new GridLayout(4, false));
regexTextScrolledComposite.setContent(textFieldsComposite);
regexTextScrolledComposite.setExpandHorizontal(true);
// To position the column labels properly, add a dummy column and use its children's sizes for reference.
// This is necessary since expressionTableLabels can't share a layout with textFieldsComposite.
textListenersEnabled = false;
addColumn(null);
data = new GridData(SWT.FILL, SWT.FILL, false, false);
data.horizontalIndent = textFieldsComposite.getChildren()[2].getLocation().x;
data.widthHint = textFieldsComposite.getChildren()[2].getSize().x;
label.setLayoutData(data);
label2.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
removeColumn(false);
textListenersEnabled = true;
}
private IDataSet getCurrentDataset() {
return DataSetFactory.createDataSet(RowDataSet.ID, columnNamesList.get(selectedRegex).toArray(new String[] {}));
}
private void createGraphCreateArea(Composite comp) {
comp.setLayout(new GridLayout(2, false));
graphsTable = new Table(comp, SWT.SINGLE | SWT.BORDER);
GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
graphsTable.setLayoutData(layoutData);
// Button to add another graph
Composite buttonComposite = new Composite(comp, SWT.NONE);
buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 1;
buttonComposite.setLayout(gridLayout);
// Button to add a new graph
addGraphButton = new Button(buttonComposite, SWT.PUSH);
addGraphButton.setText(Messages.SystemTapScriptGraphOptionsTab_AddGraphButton);
addGraphButton.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_AddGraphButtonToolTip);
addGraphButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
// Button to copy an existing graph
duplicateGraphButton = new Button(buttonComposite, SWT.PUSH);
duplicateGraphButton.setText(Messages.SystemTapScriptGraphOptionsTab_DuplicateGraphButton);
duplicateGraphButton.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_DuplicateGraphButtonToolTip);
duplicateGraphButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
// Button to edit an existing graph
editGraphButton = new Button(buttonComposite, SWT.PUSH);
editGraphButton.setText(Messages.SystemTapScriptGraphOptionsTab_EditGraphButton);
editGraphButton.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_EditGraphButtonToolTip);
editGraphButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
// Button to remove the selected graph/filter
removeGraphButton = new Button(buttonComposite, SWT.PUSH);
removeGraphButton.setText(Messages.SystemTapScriptGraphOptionsTab_RemoveGraphButton);
removeGraphButton.setToolTipText(Messages.SystemTapScriptGraphOptionsTab_RemoveGraphButtonToolTip);
removeGraphButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
// Action to notify the buttons when to enable/disable themselves based
// on list selection
graphsTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
selectedTableItem = (TableItem) e.item;
setSelectionControlsEnabled(true);
}
});
// Brings up a new dialog box when user clicks the add button. Allows
// selecting a new graph to display.
addGraphButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
SelectGraphAndSeriesWizard wizard = new SelectGraphAndSeriesWizard(getCurrentDataset(), null);
IWorkbench workbench = PlatformUI.getWorkbench();
wizard.init(workbench, null);
WizardDialog dialog = new WizardDialog(workbench
.getActiveWorkbenchWindow().getShell(), wizard);
dialog.create();
dialog.open();
GraphData gd = wizard.getGraphData();
if (gd != null) {
TableItem item = new TableItem(graphsTable, SWT.NONE);
graphsData.add(gd);
setUpGraphTableItem(item, gd, false);
updateLaunchConfigurationDialog();
}
}
});
// Adds a new entry to the list of graphs that is a copy of the one selected.
duplicateGraphButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
GraphData gd = ((GraphData) selectedTableItem.getData()).getCopy();
TableItem item = new TableItem(graphsTable, SWT.NONE);
graphsData.add(gd);
if (badGraphs.contains(selectedTableItem.getData())) {
badGraphs.add(gd);
setUpGraphTableItem(item, gd, true);
} else {
setUpGraphTableItem(item, gd, false);
}
updateLaunchConfigurationDialog();
}
});
// When button is clicked, brings up same wizard as the one for adding
// a graph. Data in the wizard is filled out to match the properties
// of the selected graph.
editGraphButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
SelectGraphAndSeriesWizard wizard = new SelectGraphAndSeriesWizard(getCurrentDataset(),
(GraphData) selectedTableItem.getData());
IWorkbench workbench = PlatformUI.getWorkbench();
wizard.init(workbench, null);
WizardDialog dialog = new WizardDialog(workbench
.getActiveWorkbenchWindow().getShell(), wizard);
dialog.create();
dialog.open();
GraphData gd = wizard.getGraphData();
if (gd == null) {
return;
}
GraphData old_gd = (GraphData) selectedTableItem.getData();
if (!gd.isCopyOf(old_gd)) {
badGraphs.remove(old_gd);
setUpGraphTableItem(selectedTableItem, gd, false);
graphsData.set(graphsTable.indexOf(selectedTableItem), gd);
checkErrors(selectedRegex);
updateLaunchConfigurationDialog();
}
}
});
// Removes the selected graph/filter from the table
removeGraphButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
GraphData gd = (GraphData) selectedTableItem.getData();
graphsData.remove(gd);
badGraphs.remove(gd);
selectedTableItem.dispose();
setSelectionControlsEnabled(false);
checkErrors(selectedRegex);
updateLaunchConfigurationDialog();
}
});
}
private void removeRegex(boolean autoSelect) {
int removedRegex = selectedRegex;
if (autoSelect) {
// The current selection is to be removed, so select something else that will be available.
regularExpressionCombo.select(selectedRegex != 0 ? selectedRegex - 1 : 1);
updateRegexSelection(regularExpressionCombo.getSelectionIndex(), false);
}
regularExpressionCombo.remove(removedRegex);
outputList.remove(removedRegex);
regexErrorMessages.remove(removedRegex);
columnNamesList.remove(removedRegex);
cachedNamesList.remove(removedRegex);
graphsDataList.remove(removedRegex);
if (autoSelect) {
// Make sure the index of the selection is accurate.
selectedRegex = regularExpressionCombo.getSelectionIndex();
}
// Re-add the "Add New Regex" entry if it is missing.
if (getNumberOfRegexs() == MAX_NUMBER_OF_REGEXS - 1) {
regularExpressionCombo.add(Messages.SystemTapScriptGraphOptionsTab_regexAddNew);
}
// Disable the "remove" button if only one selection is left; never want zero items.
if (getNumberOfRegexs() == 1) {
removeRegexButton.setEnabled(false);
}
updateLaunchConfigurationDialog();
}
/**
* This handles UI & list updating whenever a different regular expression is selected.
* @param newSelection The index of the regex to be selected.
* @param force If true, the UI will update even if the index of the selected regex did not change.
*/
private void updateRegexSelection(int newSelection, boolean force) {
// Quit if the selection didn't change anything, or if the selection is invalid (-1).
if (newSelection == -1 || (!force && selectedRegex == newSelection)) {
return;
}
selectedRegex = newSelection;
boolean textListenersDisabled = !textListenersEnabled;
if (!textListenersDisabled) {
textListenersEnabled = false;
}
sampleOutputText.setText(outputList.get(selectedRegex));
cachedNames = cachedNamesList.get(selectedRegex);
// Update the number of columns and their titles here, and not in refreshRegexRows,
// using the list of saved active names instead of a cachedNames stack.
List<String> columnNames = columnNamesList.get(selectedRegex);
int desiredNumberOfColumns = columnNames.size();
// Remove all columns to easily update them all immediately afterwards.
while (numberOfVisibleColumns > 0) {
removeColumn(false);
}
while (numberOfVisibleColumns < desiredNumberOfColumns) {
addColumn(columnNames.get(numberOfVisibleColumns));
}
refreshRegexRows();
// Now, only display graphs that are associated with the selected regex.
graphsData = graphsDataList.get(selectedRegex);
graphsTable.removeAll();
selectedTableItem = null;
setSelectionControlsEnabled(false);
for (GraphData gd : graphsData) {
TableItem item = new TableItem(graphsTable, SWT.NONE);
setUpGraphTableItem(item, gd, badGraphs.contains(gd));
}
graphsGroup.setText(MessageFormat.format(Messages.SystemTapScriptGraphOptionsTab_graphSetTitleBase,
selectedRegex + 1));
if (!textListenersDisabled) {
textListenersEnabled = true;
}
}
private void refreshRegexRows() {
try {
pattern = Pattern.compile(regularExpressionCombo.getText());
matcher = pattern.matcher(sampleOutputText.getText());
regexErrorMessages.set(selectedRegex, null);
} catch (PatternSyntaxException e) {
regexErrorMessages.set(selectedRegex, e.getMessage());
return;
}
regexErrorMessages.set(selectedRegex, checkRegex(regularExpressionCombo.getText()));
if (regexErrorMessages.get(selectedRegex) != null) {
return;
}
int desiredNumberOfColumns = matcher.groupCount();
while (numberOfVisibleColumns < desiredNumberOfColumns) {
addColumn(null);
}
while (numberOfVisibleColumns > desiredNumberOfColumns) {
removeColumn(true);
}
// Set values
Control[] children = textFieldsComposite.getChildren();
for (int i = 0; i < numberOfVisibleColumns; i++) {
String sampleOutputResults;
if (matcher.matches()) {
sampleOutputResults = matcher.group(i+1);
}
else if (sampleOutputText.getText().length() == 0) {
sampleOutputResults = Messages.SystemTapScriptGraphOptionsTab_sampleOutputIsEmpty;
} else {
sampleOutputResults = Messages.SystemTapScriptGraphOptionsTab_sampleOutputNoMatch;
}
((Label)children[i*4+3]).setText(" " + sampleOutputResults); //$NON-NLS-1$
}
// May only add/edit graphs if there is output data being captured.
addGraphButton.setEnabled(numberOfVisibleColumns > 0);
if (selectedTableItem != null) {
editGraphButton.setEnabled(numberOfVisibleColumns > 0);
}
regexErrorMessages.set(selectedRegex, findBadGraphs(selectedRegex));
}
/**
* Checks if a provided regular expression is valid.
* @param regex The regular expression to check for validity.
* @return <code>null</code> if the regular expression is valid, or an error message.
*/
private static String checkRegex(String regex) {
//TODO may add more invalid regexs here, each with its own error message.
if (regex.contains("()")) { //$NON-NLS-1$
return Messages.SystemTapScriptGraphOptionsTab_emptyGroup;
}
return null;
}
/**
* Adds one column to the list of the currently-selected regex's columns.
* This creates an extra Text in which the name of the column may be entered,
* and a corresponding Label containing sample expected output.
* @param nameToAdd If non-null, the name of the newly-created column will
* match this String. If null, the column will be given a name recovered from
* the active stack of cached names, or a default name if one doesn't exist.
*/
private void addColumn(String nameToAdd) {
// Show the "shift" buttons of the previous column, if they exist.
if (this.numberOfVisibleColumns > 0) {
textFieldsComposite.getChildren()[(this.numberOfVisibleColumns - 1) * 4].setVisible(true);
textFieldsComposite.getChildren()[(this.numberOfVisibleColumns - 1) * 4 + 1].setVisible(true);
}
// Add buttons for shifting column names up/down in the list.
Button buttonUp = new Button(textFieldsComposite, SWT.PUSH);
buttonUp.setText(Messages.SystemTapScriptGraphOptionsTab_columnShiftUp);
buttonUp.setVisible(false);
Button buttonDown = new Button(textFieldsComposite, SWT.PUSH);
buttonDown.setText(Messages.SystemTapScriptGraphOptionsTab_columnShiftDown);
buttonDown.setVisible(false);
Text text = new Text(textFieldsComposite, SWT.BORDER);
GridData data = new GridData(SWT.FILL, SWT.FILL, false, false);
data.minimumWidth = 200;
data.widthHint = 200;
text.setLayoutData(data);
numberOfVisibleColumns++;
text.addModifyListener(columnNameListener);
if (nameToAdd == null) {
// Restore a deleted name by popping from the stack.
if (cachedNames.size() > 0) {
text.setText(cachedNames.pop());
} else {
text.setText(MessageFormat.format(Messages.SystemTapScriptGraphOptionsTab_defaultColumnTitleBase,
numberOfVisibleColumns));
}
} else {
text.setText(nameToAdd);
}
Label label = new Label(textFieldsComposite, SWT.BORDER);
label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
textFieldsComposite.layout();
textFieldsComposite.pack();
// Add button listeners for shifting column names.
buttonUp.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Control clickedButton = (Control) e.widget;
Control[] children = textFieldsComposite.getChildren();
int currentColumn = 0;
for (; currentColumn < numberOfVisibleColumns - 1; currentColumn++) {
if (children[currentColumn*4].equals(clickedButton)) {
break;
}
}
String edgeName = ((Text)children[currentColumn*4 + 2]).getText();
for (int i = currentColumn; i < numberOfVisibleColumns - 1; i++) {
((Text)children[i*4 + 2]).setText(((Text)children[(i + 1)*4 + 2]).getText());
}
((Text)children[(numberOfVisibleColumns - 1)*4 + 2]).setText(edgeName);
}
});
buttonDown.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Control clickedButton = (Control) e.widget;
Control[] children = textFieldsComposite.getChildren();
int currentColumn = 0;
for (; currentColumn < numberOfVisibleColumns - 1; currentColumn++) {
if (children[currentColumn*4 + 1].equals(clickedButton)) {
break;
}
}
String edgeName = ((Text)children[(numberOfVisibleColumns - 1)*4 + 2]).getText();
for (int i = numberOfVisibleColumns - 1; i > currentColumn; i--) {
((Text)children[i*4 + 2]).setText(((Text)children[(i - 1)*4 + 2]).getText());
}
((Text)children[currentColumn*4 + 2]).setText(edgeName);
}
});
}
/**
* Removes a column from the currently-selected regex, and removes its
* corresponding Text & Label from the UI.
* @param saveNames Set to <code>true</code> if the contents of removed
* columns are to be saved in a stack for later use.
*/
private void removeColumn(boolean saveNames) {
Control[] children = textFieldsComposite.getChildren();
int i = this.numberOfVisibleColumns*4 - 1;
if (saveNames) {
// Push the removed name on a stack.
String name = ((Text)children[i-1]).getText();
if (name != null && name != "") { //$NON-NLS-1$
cachedNames.push(name);
}
columnNamesList.get(selectedRegex).remove(numberOfVisibleColumns - 1);
}
children[i].dispose();
children[i-1].dispose();
children[i-2].dispose();
children[i-3].dispose();
// Hide the previous column's "shift" buttons, if it exists.
if (this.numberOfVisibleColumns > 2) {
children[i - 6].setVisible(false);
children[i - 7].setVisible(false);
}
this.numberOfVisibleColumns--;
textFieldsComposite.layout();
textFieldsComposite.pack();
}
/**
* Marks all graphs belonging to the indicated regular expression that have an
* error (missing column data, invalid graphID), or unmarks graphs that don't.
* @param regex The index of the regular expression to check for invalid graphs.
* @return An appropriate error message if an invalid graph is found, or if the
* selected regular expression parses nothing.
*/
private String findBadGraphs(int regex) {
boolean foundBadID = false;
boolean foundRemoved = false;
int numberOfColumns = columnNamesList.get(regex).size();
for (GraphData gd : graphsDataList.get(regex)) {
boolean singleBadID = false;
boolean singleRemoved = false;
if (GraphFactory.getGraphName(gd.graphID) == null) {
singleBadID = true;
} else {
if (gd.xSeries >= numberOfColumns) {
singleRemoved = true;
}
for (int s = 0; s < gd.ySeries.length && !singleRemoved; s++) {
if (gd.ySeries[s] >= numberOfColumns) {
singleRemoved = true;
}
}
}
if (singleRemoved || singleBadID) {
if (!badGraphs.contains(gd)) {
badGraphs.add(gd);
setUpGraphTableItem(findGraphTableItem(gd), null, true);
}
} else if (badGraphs.contains(gd)) {
badGraphs.remove(gd);
setUpGraphTableItem(findGraphTableItem(gd), null, false);
}
foundBadID |= singleBadID;
foundRemoved |= singleRemoved;
}
if (numberOfColumns == 0) {
return Messages.SystemTapScriptGraphOptionsTab_noGroups;
}
if (foundBadID) {
return Messages.SystemTapScriptGraphOptionsTab_badGraphID;
}
if (foundRemoved) {
return Messages.SystemTapScriptGraphOptionsTab_deletedGraphData;
}
return null;
}
private TableItem findGraphTableItem(GraphData gd) {
for (TableItem item : graphsTable.getItems()) {
if (item.getData().equals(gd)) {
return item;
}
}
return null;
}
/**
* Sets up a given {@link TableItem} with the proper title & appearance based on
* its graph data & (in)valid status.
* @param item The {@link TableItem} to set up.
* @param gd The {@link GraphData} that the item will hold. Set to <code>null</code>
* to preserve the item's existing data.
* @param bad <code>true</code> if the item should appear as invalid, <code>false</code> otherwise.
*/
private void setUpGraphTableItem(TableItem item, GraphData gd, boolean bad) {
// Include a null check to avoid accidentally marking non-visible items.
if (item == null) {
return;
}
if (gd != null) {
item.setData(gd);
} else {
gd = (GraphData) item.getData();
}
item.setForeground(item.getDisplay().getSystemColor(bad ? SWT.COLOR_RED : SWT.COLOR_BLACK));
String graphName = GraphFactory.getGraphName(gd.graphID);
if (graphName == null) {
graphName = Messages.SystemTapScriptGraphOptionsTab_invalidGraphID;
}
item.setText(graphName + ":" + gd.title //$NON-NLS-1$
+ (bad ? " " + Messages.SystemTapScriptGraphOptionsTab_invalidGraph : "")); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(RUN_WITH_CHART, false);
configuration.setAttribute(NUMBER_OF_REGEXS, 1);
configuration.setAttribute(NUMBER_OF_COLUMNS + 0, 0);
configuration.setAttribute(NUMBER_OF_EXTRAS + 0, 0);
configuration.setAttribute(REGULAR_EXPRESSION + 0, ""); //$NON-NLS-1$
configuration.setAttribute(SAMPLE_OUTPUT + 0, ""); //$NON-NLS-1$
configuration.setAttribute(NUMBER_OF_GRAPHS + 0, 0);
}
@Override
public void initializeFrom(ILaunchConfiguration configuration) {
try {
textListenersEnabled = false;
// Reset lists & settings to keep things idempotent.
regularExpressionCombo.removeAll();
outputList.clear();
regexErrorMessages.clear();
columnNamesList.clear();
cachedNamesList.clear();
graphsTable.removeAll();
badGraphs.clear();
// There should always be at least one regular expression (a blank one still counts).
// If configuration's number of regexs is zero, it is outdated.
int numberOfRegexs = Math.max(configuration.getAttribute(NUMBER_OF_REGEXS, 1), 1);
// Only allow removing regexs if there are more than one.
removeRegexButton.setEnabled(numberOfRegexs > 1);
for (int r = 0; r < numberOfRegexs; r++) {
// Save all of the configuration's regular expressions & sample outputs in a list.
regularExpressionCombo.add(configuration.getAttribute(REGULAR_EXPRESSION + r, "")); //$NON-NLS-1$
outputList.add(configuration.getAttribute(SAMPLE_OUTPUT + r, "")); //$NON-NLS-1$
// Save each regex's list of group names.
int numberOfColumns = configuration.getAttribute(NUMBER_OF_COLUMNS + r, 0);
ArrayList<String> namelist = new ArrayList<>(numberOfColumns);
for (int i = 0; i < numberOfColumns; i++) {
namelist.add(configuration.getAttribute(get2DConfigData(REGEX_BOX, r, i), (String)null));
}
columnNamesList.add(namelist);
//Reclaim missing column data that was required for existing graphs at the time of the previous "apply".
int numberOfExtras = configuration.getAttribute(NUMBER_OF_EXTRAS + r, 0);
Stack<String> oldnames = new Stack<>();
for (int i = 0; i < numberOfExtras; i++) {
oldnames.push(configuration.getAttribute(get2DConfigData(EXTRA_BOX, r, i), (String)null));
}
cachedNamesList.add(oldnames);
regexErrorMessages.add(null);
}
if (getNumberOfRegexs() < MAX_NUMBER_OF_REGEXS) {
regularExpressionCombo.add(Messages.SystemTapScriptGraphOptionsTab_regexAddNew);
}
// When possible, preserve the selection on subsequent initializations, for user convenience.
int defaultSelectedRegex = 0 <= selectedRegex && selectedRegex < numberOfRegexs ? selectedRegex : 0;
regularExpressionCombo.select(defaultSelectedRegex);
// Add graphs
graphsDataList = createGraphsFromConfiguration(configuration);
graphsData = graphsDataList.get(defaultSelectedRegex);
for (GraphData graphData : graphsData) {
TableItem item = new TableItem(graphsTable, SWT.NONE);
setUpGraphTableItem(item, graphData, true);
}
updateRegexSelection(defaultSelectedRegex, true); // Handles all remaining updates.
checkAllOtherErrors();
boolean chart = configuration.getAttribute(RUN_WITH_CHART, false);
setGraphingEnabled(chart);
this.runWithChartCheckButton.setSelection(chart);
} catch (CoreException e) {
ExceptionErrorDialog.openError(Messages.SystemTapScriptGraphOptionsTab_cantInitializeTab, e);
} finally {
textListenersEnabled = true;
}
}
@Override
public void performApply(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(RUN_WITH_CHART, this.runWithChartCheckButton.getSelection());
int numberOfRegexs = getNumberOfRegexs();
for (int r = 0; r < numberOfRegexs; r++) {
// Save data sets, and clear removed ones.
configuration.setAttribute(REGULAR_EXPRESSION + r, regularExpressionCombo.getItem(r));
configuration.setAttribute(SAMPLE_OUTPUT + r, outputList.get(r));
List<String> columnNames = columnNamesList.get(r);
int numberOfColumns = columnNames.size();
for (int i = 0; i < numberOfColumns; i++) {
configuration.setAttribute(get2DConfigData(REGEX_BOX, r, i), columnNames.get(i));
}
cleanUpConfigurationItem(configuration, NUMBER_OF_COLUMNS, REGEX_BOX, r, numberOfColumns);
configuration.setAttribute(NUMBER_OF_COLUMNS + r, numberOfColumns);
// If the current regex has graphs with missing data, store all cached names
// in the configuration so that they will be easily restorable for next time.
Stack<String> extranames = cachedNamesList.get(r);
int numberOfExtras = findBadGraphs(r) == null ? 0 : extranames.size();
for (int i = 0; i < numberOfExtras; i++) {
configuration.setAttribute(get2DConfigData(EXTRA_BOX, r, i), extranames.get(i));
}
cleanUpConfigurationItem(configuration, NUMBER_OF_EXTRAS, EXTRA_BOX, r, numberOfExtras);
configuration.setAttribute(NUMBER_OF_EXTRAS + r, numberOfExtras);
// Save new graphs, and clear removed ones.
LinkedList<GraphData> list = graphsDataList.get(r);
int numberOfGraphs = list.size();
for (int i = 0; i < numberOfGraphs; i++) {
GraphData graphData = list.get(i);
configuration.setAttribute(get2DConfigData(GRAPH_TITLE, r, i), graphData.title);
configuration.setAttribute(get2DConfigData(GRAPH_KEY, r, i), graphData.key);
configuration.setAttribute(get2DConfigData(GRAPH_X_SERIES, r, i), graphData.xSeries);
configuration.setAttribute(get2DConfigData(GRAPH_ID, r, i), graphData.graphID);
int ySeriesLength = graphData.ySeries.length;
for (int j = 0; j < ySeriesLength; j++) {
configuration.setAttribute(get2DConfigData(GRAPH_Y_SERIES, r, i + "_" + j), //$NON-NLS-1$
graphData.ySeries[j]);
}
cleanUpConfigurationGraphYSeries(configuration, r, i, ySeriesLength);
configuration.setAttribute(get2DConfigData(GRAPH_Y_SERIES_LENGTH, r, i), ySeriesLength);
}
cleanUpConfigurationGraphs(configuration, r, numberOfGraphs);
configuration.setAttribute(NUMBER_OF_GRAPHS + r, numberOfGraphs);
}
cleanUpConfiguration(configuration, numberOfRegexs);
configuration.setAttribute(NUMBER_OF_REGEXS, numberOfRegexs);
}
/**
* Removes all configuration attributes associated with deleted regular expressions.
* @param configuration The configuration to remove attributes from.
* @param numberOfRegexs The number of regex-related properties to exist in the
* configuration after cleanup.
*/
private void cleanUpConfiguration(ILaunchConfigurationWorkingCopy configuration, int numberOfRegexs) {
int oldNumberOfRegexs = 0;
try {
oldNumberOfRegexs = configuration.getAttribute(NUMBER_OF_REGEXS, 0);
} catch (CoreException e) {}
for (int r = numberOfRegexs; r < oldNumberOfRegexs; r++) {
configuration.removeAttribute(REGULAR_EXPRESSION + r);
configuration.removeAttribute(SAMPLE_OUTPUT + r);
cleanUpConfigurationItem(configuration, NUMBER_OF_COLUMNS, REGEX_BOX, r, 0);
configuration.removeAttribute(NUMBER_OF_COLUMNS + r);
cleanUpConfigurationItem(configuration, NUMBER_OF_COLUMNS, EXTRA_BOX, r, 0);
configuration.removeAttribute(NUMBER_OF_EXTRAS + r);
cleanUpConfigurationGraphs(configuration, r, 0);
configuration.removeAttribute(NUMBER_OF_GRAPHS + r);
}
}
private void cleanUpConfigurationGraphs(ILaunchConfigurationWorkingCopy configuration, int regex, int newNumberOfGraphs) {
int oldNumberOfGraphs = 0;
try {
oldNumberOfGraphs = configuration.getAttribute(NUMBER_OF_GRAPHS + regex, 0);
} catch (CoreException e) {}
for (int i = newNumberOfGraphs; i < oldNumberOfGraphs; i++) {
configuration.removeAttribute(get2DConfigData(GRAPH_TITLE, regex, i));
configuration.removeAttribute(get2DConfigData(GRAPH_KEY, regex, i));
configuration.removeAttribute(get2DConfigData(GRAPH_X_SERIES, regex, i));
configuration.removeAttribute(get2DConfigData(GRAPH_ID, regex, i));
cleanUpConfigurationGraphYSeries(configuration, regex, i, 0);
configuration.removeAttribute(get2DConfigData(GRAPH_Y_SERIES_LENGTH, regex, i));
}
}
private void cleanUpConfigurationItem(ILaunchConfigurationWorkingCopy configuration, String counter, String property, int regex, int newNumberOfItems) {
int oldNumberOfItems = 0;
try {
oldNumberOfItems = configuration.getAttribute(counter + regex, 0);
} catch (CoreException e) {}
for (int i = newNumberOfItems; i < oldNumberOfItems; i++) {
configuration.removeAttribute(get2DConfigData(property, regex, i));
}
}
private void cleanUpConfigurationGraphYSeries(ILaunchConfigurationWorkingCopy configuration, int regex, int graph, int newLength) {
int oldYSeriesLength = 0;
try {
oldYSeriesLength = configuration.getAttribute(get2DConfigData(GRAPH_Y_SERIES_LENGTH, regex, graph), 0);
} catch (CoreException e) {}
for (int i = newLength; i < oldYSeriesLength; i++) {
configuration.removeAttribute(get2DConfigData(GRAPH_Y_SERIES, regex, graph + "_" + i)); //$NON-NLS-1$
}
}
/**
* Checks all regular expressions for errors, except for the currently-selected
* expression (as it should be checked by {@link #refreshRegexRows}).
*/
private void checkAllOtherErrors() {
for (int i = 0, n = getNumberOfRegexs(); i < n; i++) {
if (i == selectedRegex) {
continue;
}
checkErrors(i);
}
}
/**
* Checks the regular expression of the provided index for errors.
* Sets the associated error message to contain relevant error information.
* @param i The index of the regular expression to check for errors.
*/
private void checkErrors(int i) {
String regex = regularExpressionCombo.getItem(i);
try {
Pattern.compile(regex);
} catch (PatternSyntaxException e) {
regexErrorMessages.set(i, e.getMessage());
return;
}
String error = findBadGraphs(i);
if (error == null) {
error = checkRegex(regex);
}
regexErrorMessages.set(i, error);
}
@Override
public boolean isValid(ILaunchConfiguration launchConfig) {
setErrorMessage(null);
// If graphic is disabled then everything is valid.
if (!this.graphingEnabled) {
return true;
}
for (int r = 0, n = getNumberOfRegexs(); r < n; r++) {
String regexErrorMessage = regexErrorMessages.get(r);
if (regexErrorMessage != null) {
setErrorMessage(MessageFormat.format(Messages.SystemTapScriptGraphOptionsTab_regexErrorMsgFormat,
regularExpressionCombo.getItems()[r], regexErrorMessage));
return false;
}
}
return true;
}
/**
* Checks if a launch configuration's Systemtap Graphing settings are valid.
* @param launchConfig The launch configuration to check for graph validity.
* @return <code>true</code> if the launch settings are valid, or <code>false</code> if
* its graph settings are invalid in some way.
* @since 2.2
*/
public static boolean isValidLaunch(ILaunchConfiguration launchConfig) throws CoreException {
// If graphic is disabled then everything is valid.
if (!launchConfig.getAttribute(RUN_WITH_CHART, false)) {
return true;
}
for (int r = 0, n = launchConfig.getAttribute(NUMBER_OF_REGEXS, 1); r < n; r++) {
// Check for any invalid regexs.
String regex = launchConfig.getAttribute(REGULAR_EXPRESSION + r, (String) null);
if (regex == null || checkRegex(regex) != null) {
return false;
}
try {
Pattern.compile(regex);
} catch (PatternSyntaxException e) {
return false;
}
// If graphs are plotted but no data is captured by one of them, report this as a problem.
int numberOfColumns = launchConfig.getAttribute(NUMBER_OF_COLUMNS + r, 0);
if (numberOfColumns == 0) {
return false;
}
// Check for graphs that are missing required data.
for (int i = 0, g = launchConfig.getAttribute(NUMBER_OF_GRAPHS + r, 0); i < g; i++) {
if (GraphFactory.getGraphName(launchConfig.getAttribute(get2DConfigData(GRAPH_ID, r, i), (String) null)) == null) {
return false;
}
if (launchConfig.getAttribute(get2DConfigData(GRAPH_X_SERIES, r, i), 0) >= numberOfColumns) {
return false;
}
for (int j = 0, y = launchConfig.getAttribute(get2DConfigData(GRAPH_Y_SERIES_LENGTH, r, i), 0); j < y; j++) {
if (launchConfig.getAttribute(get2DConfigData(GRAPH_Y_SERIES, r, i + "_" + j), 0) >= numberOfColumns) { //$NON-NLS-1$
return false;
}
}
}
}
return true;
}
@Override
public String getName() {
return Messages.SystemTapScriptGraphOptionsTab_graphingTitle;
}
@Override
public Image getImage() {
return AbstractUIPlugin.imageDescriptorFromPlugin(IDEPlugin.PLUGIN_ID,
"icons/graphing_tab.gif").createImage(); //$NON-NLS-1$
}
private void setGraphingEnabled(boolean enabled) {
this.graphingEnabled = enabled;
this.setControlEnabled(outputParsingGroup, enabled);
this.setControlEnabled(graphsGroup, enabled);
// Disable buttons that rely on a selected graph if no graph is selected.
this.setSelectionControlsEnabled(selectedTableItem != null);
this.addGraphButton.setEnabled(enabled && numberOfVisibleColumns > 0);
this.removeRegexButton.setEnabled(enabled && getNumberOfRegexs() > 1);
updateLaunchConfigurationDialog();
}
private void setControlEnabled(Composite composite, boolean enabled) {
composite.setEnabled(enabled);
for (Control child : composite.getChildren()) {
child.setEnabled(enabled);
if (child instanceof Composite) {
setControlEnabled((Composite)child, enabled);
}
}
}
/**
* Call this to enable/disable all buttons whose actions depend on a selected graph.
* @param enabled Set to true to enable the buttons; set to false to disable them.
*/
private void setSelectionControlsEnabled(boolean enabled) {
duplicateGraphButton.setEnabled(enabled);
editGraphButton.setEnabled(enabled && numberOfVisibleColumns > 0);
removeGraphButton.setEnabled(enabled);
}
}