/******************************************************************************* * 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); } }