package org.checkerframework.eclipse.ui;
import static org.checkerframework.eclipse.ui.CheckerPreferencePage.PT_COLUMNS.*;
import java.util.*;
import org.checkerframework.eclipse.CheckerPlugin;
import org.checkerframework.eclipse.actions.CheckerInfo;
import org.checkerframework.eclipse.actions.CheckerManager;
import org.checkerframework.eclipse.prefs.CheckerPreferences;
import org.checkerframework.eclipse.prefs.OptionLine;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.internal.ui.dialogs.OpenTypeSelectionDialog;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SelectionDialog;
public class CheckerPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
private static final String BUILT_IN_LABEL = "Built-In";
private static final String CUSTOM_LABEL = "Custom";
protected enum PT_COLUMNS {
LABEL,
SOURCE,
CLASSES
}
private Table procTable;
private Table optTable;
private Text argText;
private Text optSkipUses;
private Text optALint;
private Text optFilter;
private Button optVerbose;
private Text optJDKPath;
// private Button optAutoBuild;
private Button optWarning;
private Button optFilenames;
private Button optNoMsgText;
private Button optShowChecks;
private Button optImplicitImports;
@Override
public void init(IWorkbench workbench) {}
@Override
protected IPreferenceStore doGetPreferenceStore() {
return CheckerPlugin.getDefault().getPreferenceStore();
}
@Override
protected Control createContents(Composite parent) {
// Layout for pref page
final Composite tableComposite = new Composite(parent, SWT.None);
final GridLayout layout = new GridLayout();
tableComposite.setLayout(layout);
makeProcessorGroup(tableComposite);
// UI/Eclipse options
final Group uiGroup = new Group(tableComposite, SWT.None);
uiGroup.setText("Eclipse options");
final FillLayout uiLayout = new FillLayout(SWT.VERTICAL);
uiLayout.marginWidth = uiLayout.marginHeight = 5;
uiGroup.setLayout(uiLayout);
// optAutoBuild = new Button(uiGroup, SWT.CHECK);
// optAutoBuild.setText("Automatically run type-checkers");
final Label filterLabel = new Label(uiGroup, SWT.None);
filterLabel.setText("Regex for warning/error filter:");
optFilter = new Text(uiGroup, SWT.SINGLE | SWT.BORDER);
final GridData uiGridData = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
uiGroup.setLayoutData(uiGridData);
optVerbose = new Button(uiGroup, SWT.CHECK);
optVerbose.setText("Show verbose output");
// JDK options
final Group jdkGroup = new Group(tableComposite, SWT.None);
jdkGroup.setText("JDK options");
final FormLayout jdkLayout = new FormLayout();
jdkLayout.marginWidth = jdkLayout.marginHeight = 5;
jdkGroup.setLayout(jdkLayout);
final Label jdkFolderLabel = new Label(jdkGroup, SWT.None);
jdkFolderLabel.setText("Java Home Directory:");
optJDKPath = new Text(jdkGroup, SWT.SINGLE | SWT.BORDER);
final Button browseButton = new Button(jdkGroup, SWT.PUSH);
browseButton.setText("Browse...");
browseButton.addSelectionListener(
new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
DirectoryDialog dirDialog =
new DirectoryDialog(
PlatformUI.getWorkbench()
.getActiveWorkbenchWindow()
.getShell(),
SWT.OPEN);
String path = dirDialog.open();
optJDKPath.setText(path);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {}
});
final FormData jdkFormData1 = new FormData();
jdkFormData1.left = new FormAttachment(0, 5);
jdkFormData1.right = new FormAttachment(100, 0);
jdkFolderLabel.setLayoutData(jdkFormData1);
final FormData jdkFormData2 = new FormData();
jdkFormData2.top = new FormAttachment(jdkFolderLabel, 5);
jdkFormData2.left = new FormAttachment(0, 5);
jdkFormData2.right = new FormAttachment(80, -5);
optJDKPath.setLayoutData(jdkFormData2);
final FormData jdkFormData3 = new FormData();
jdkFormData3.top = new FormAttachment(jdkFolderLabel, 5);
jdkFormData3.left = new FormAttachment(optJDKPath, 5);
browseButton.setLayoutData(jdkFormData3);
final GridData jdkGridData = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
jdkGridData.widthHint = 300;
jdkGroup.setLayoutData(jdkGridData);
// Processor options
final Group procGroup = new Group(tableComposite, SWT.None);
procGroup.setText("Processor/build options");
FillLayout procLayout = new FillLayout(SWT.VERTICAL);
procLayout.marginWidth = procLayout.marginHeight = 5;
procGroup.setLayout(procLayout);
final Label skipLabel = new Label(procGroup, SWT.None);
skipLabel.setText("Classes to skip (-AskipUses):");
optSkipUses = new Text(procGroup, SWT.SINGLE | SWT.BORDER);
optSkipUses.setToolTipText("Classes to skip during type-checking (-AskipUses)");
final Label lintLabel = new Label(procGroup, SWT.None);
lintLabel.setText("Lint options:");
optALint = new Text(procGroup, SWT.SINGLE | SWT.BORDER);
optALint.setToolTipText("Enable or disable optional checks (-Alint)");
optWarning = new Button(procGroup, SWT.CHECK);
optWarning.setText("Show errors as warnings (-Awarns)");
optFilenames = new Button(procGroup, SWT.CHECK);
optFilenames.setText("Print the name of each file (-Afilenames)");
optNoMsgText = new Button(procGroup, SWT.CHECK);
optNoMsgText.setText("Use message keys instead of text (-Anomsgtext)");
optShowChecks = new Button(procGroup, SWT.CHECK);
optShowChecks.setText("Print debugging info for pseudo-checks (-Ashowchecks)");
optImplicitImports = new Button(procGroup, SWT.CHECK);
optImplicitImports.setText("Use implicit imports for annotation classes");
GridData procGridData = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
procGroup.setLayoutData(procGridData);
// argText = new Text(javacGroup, SWT.SINGLE | SWT.BORDER);
makeCompilerParameters(tableComposite);
initValues();
return tableComposite;
}
private void makeProcessorGroup(final Composite tableComposite) {
final Group group = new Group(tableComposite, SWT.None);
group.setText("Checkers");
// Layout info for tableComposite's layout
GridData groupGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
group.setLayoutData(groupGridData);
// group's layout
final GridLayout layout = new GridLayout();
layout.numColumns = 2;
group.setLayout(layout);
// Make Processor table
procTable = new Table(group, SWT.CHECK | SWT.MULTI | SWT.BORDER);
// layout data within the group
final GridData procTableData = new GridData(SWT.FILL, SWT.TOP, true, false);
procTableData.heightHint = 200;
procTableData.horizontalSpan = 2;
procTableData.horizontalSpan = SWT.FILL;
procTableData.grabExcessHorizontalSpace = true;
procTable.setLayoutData(procTableData);
procTable.setLinesVisible(true);
procTable.setHeaderVisible(true);
final List<String> headers = Arrays.asList("Name", "Source", "Class");
for (final String header : headers) {
final TableColumn column = new TableColumn(procTable, SWT.NONE);
column.setText(header);
}
// Add built-in checkers to the table
for (final CheckerInfo checkerInfo : CheckerManager.getCheckerInfos()) {
addProcTableItem(checkerInfo, true);
}
for (final String className : CheckerManager.getStoredCustomClasses()) {
addProcTableItem(CheckerInfo.fromClassPath(className, null), false);
}
for (final TableColumn columns : procTable.getColumns()) {
columns.pack();
}
// Make the add/remove controls for the table
final Button addButton = new Button(group, SWT.PUSH);
addButton.setText("Add");
addButton.addSelectionListener(
new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
searchForClass();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {}
});
final Button removeButton = new Button(group, SWT.PUSH);
removeButton.setText("Remove");
removeButton.setEnabled(false);
removeButton.addSelectionListener(
new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
removePTIndices(procTable.getSelectionIndices());
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {}
});
final GridData removeGd = new GridData();
removeGd.horizontalAlignment = SWT.END;
removeButton.setLayoutData(removeGd);
// enable/disable the remove button (enabled only there are NO
// built-in checkers enabled and when at least 1 custom checker is
// selected)
procTable.addSelectionListener(
new SelectionListener() {
public void widgetSelected(SelectionEvent event) {
setRemoveState();
}
public void widgetDefaultSelected(SelectionEvent event) {
setRemoveState();
}
private void setRemoveState() {
final TableItem[] selectedItems = procTable.getSelection();
boolean enabled = true;
if (selectedItems == null || selectedItems.length == 0) {
enabled = false;
} else {
for (final TableItem ti : selectedItems) {
if (!ti.getText(SOURCE.ordinal()).equals(CUSTOM_LABEL)) {
enabled = false;
break;
}
}
}
removeButton.setEnabled(enabled);
}
});
}
//TODO: Lot's of overlap with procTable, perhaps there is a way to generalize these two
//TODO: We could also move some of the other built-in options as unremovable options in this table
public void makeCompilerParameters(final Composite tableComposite) {
final Group group = new Group(tableComposite, SWT.None);
group.setText("Additional compiler parameters");
// Layout info for tableComposite's layout
GridData groupGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
group.setLayoutData(groupGridData);
// group's layout
final GridLayout layout = new GridLayout();
layout.numColumns = 2;
group.setLayout(layout);
// Make Processor table
optTable = new Table(group, SWT.CHECK | SWT.MULTI | SWT.BORDER);
// layout data within the group
final GridData optTableData = new GridData(SWT.FILL, SWT.TOP, true, false);
optTableData.heightHint = 100;
optTableData.horizontalSpan = 2;
optTableData.horizontalSpan = SWT.FILL;
optTableData.grabExcessHorizontalSpace = true;
optTable.setLayoutData(optTableData);
optTable.setLinesVisible(true);
optTable.setHeaderVisible(true);
final TableColumn column = new TableColumn(optTable, SWT.NONE);
column.setText("Option");
// Add existing op;tions to the table
for (final OptionLine optionInfo : getMiscParams()) {
addOptTableItem(optionInfo);
}
for (final TableColumn columns : optTable.getColumns()) {
columns.pack();
}
final Button addButton = new Button(group, SWT.PUSH);
addButton.setText("Add");
addButton.addSelectionListener(
new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
addOption();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {}
});
final Button removeButton = new Button(group, SWT.PUSH);
removeButton.setText("Remove");
removeButton.setEnabled(false);
removeButton.addSelectionListener(
new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
removeOptionIndices(optTable.getSelectionIndices());
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {}
});
final GridData removeGd = new GridData();
removeGd.horizontalAlignment = SWT.END;
removeButton.setLayoutData(removeGd);
// enable/disable the remove button (enabled only there are NO
// built-in checkers enabled and when at least 1 custom checker is
// selected)
optTable.addSelectionListener(
new SelectionListener() {
public void widgetSelected(SelectionEvent event) {
setRemoveState();
}
public void widgetDefaultSelected(SelectionEvent event) {
setRemoveState();
}
private void setRemoveState() {
final TableItem[] selectedItems = optTable.getSelection();
removeButton.setEnabled(selectedItems != null || selectedItems.length != 0);
}
});
}
private TableItem addOptTableItem(final OptionLine optLine) {
final TableItem item = new TableItem(optTable, SWT.None);
item.setText(0, optLine.getArgument());
item.setChecked(optLine.isActive());
return item;
}
private void removeOptionIndices(final int[] indices) {
optTable.remove(indices);
}
private void removePTIndices(final int[] indices) {
for (final int index : indices) {
final TableItem ti = procTable.getItem(index);
if (!ti.getText(SOURCE.ordinal()).equals(CUSTOM_LABEL)) {
throw new IllegalArgumentException(
"Cannot remove built-in checker " + ti.getText(LABEL.ordinal()));
}
}
procTable.remove(indices);
}
public static String[] splitAtUppercase(final String toSplit) {
List<String> tokens = new ArrayList<String>();
int length = toSplit.length();
int start = 0;
for (int i = 0; i < length; i++) {
if ((Character.isUpperCase(toSplit.charAt(i)) && i != 0)) {
tokens.add(toSplit.substring(start, i));
start = i;
}
}
tokens.add(toSplit.substring(start, length));
return tokens.toArray(new String[tokens.size()]);
}
protected List<OptionLine> getMiscParams() {
IPreferenceStore store = doGetPreferenceStore();
return OptionLine.parseOptions(store.getString(CheckerPreferences.PREF_CHECKER_ARGS));
}
protected void writeMiscParams() {
final List<OptionLine> options = new ArrayList<OptionLine>();
for (TableItem item : optTable.getItems()) {
options.add(new OptionLine(item.getText(0), item.getChecked()));
}
doGetPreferenceStore()
.setValue(
CheckerPreferences.PREF_CHECKER_ARGS,
OptionLine.optionLinesToString(options));
}
/** Initialise the values in the table to the preference values */
private void initValues() {
IPreferenceStore store = doGetPreferenceStore();
final List<String> selectedClasses =
new ArrayList<String>(CheckerManager.getSelectedClasses());
for (TableItem item : procTable.getItems()) {
int index = 0;
while (index < selectedClasses.size()) {
if (item.getText(CLASSES.ordinal()).equals(selectedClasses.get(index))) {
item.setChecked(true);
selectedClasses.remove(index);
} else {
++index;
}
}
}
/*optAutoBuild.setSelection(store
.getBoolean(CheckerPreferences.PREF_CHECKER_AUTO_BUILD)); */
optSkipUses.setText(store.getString(CheckerPreferences.PREF_CHECKER_A_SKIP_CLASSES));
optALint.setText(store.getString(CheckerPreferences.PREF_CHECKER_A_LINT));
optWarning.setSelection(store.getBoolean(CheckerPreferences.PREF_CHECKER_A_WARNS));
optFilenames.setSelection(store.getBoolean(CheckerPreferences.PREF_CHECKER_A_FILENAMES));
optNoMsgText.setSelection(store.getBoolean(CheckerPreferences.PREF_CHECKER_A_NO_MSG_TEXT));
optShowChecks.setSelection(store.getBoolean(CheckerPreferences.PREF_CHECKER_A_SHOW_CHECKS));
optFilter.setText(store.getString(CheckerPreferences.PREF_CHECKER_ERROR_FILTER_REGEX));
optVerbose.setSelection(store.getBoolean(CheckerPreferences.PREF_CHECKER_VERBOSE));
optJDKPath.setText(store.getString(CheckerPreferences.PREF_CHECKER_JDK_PATH));
optImplicitImports.setSelection(
store.getBoolean(CheckerPreferences.PREF_CHECKER_IMPLICIT_IMPORTS));
}
public boolean performOk() {
IPreferenceStore store = doGetPreferenceStore();
List<String> selectedClasses = new ArrayList<String>();
for (final TableItem ti : procTable.getItems()) {
if (ti.getChecked()) {
selectedClasses.add(ti.getText(CLASSES.ordinal()));
}
}
CheckerManager.storeSelectedClasses(selectedClasses);
writeMiscParams();
final List<String> ccFromTi = customClassesFromTableItems();
final String[] customClasses = ccFromTi.toArray(new String[ccFromTi.size()]);
CheckerManager.storeCustomClasses(customClasses);
store.setValue(CheckerPreferences.PREF_CHECKER_PREFS_SET, true);
/*store.setValue(CheckerPreferences.PREF_CHECKER_AUTO_BUILD,
optAutoBuild.getSelection()); */
store.setValue(CheckerPreferences.PREF_CHECKER_A_SKIP_CLASSES, optSkipUses.getText());
store.setValue(CheckerPreferences.PREF_CHECKER_A_LINT, optALint.getText());
store.setValue(CheckerPreferences.PREF_CHECKER_A_WARNS, optWarning.getSelection());
store.setValue(CheckerPreferences.PREF_CHECKER_A_FILENAMES, optFilenames.getSelection());
store.setValue(CheckerPreferences.PREF_CHECKER_A_NO_MSG_TEXT, optNoMsgText.getSelection());
store.setValue(CheckerPreferences.PREF_CHECKER_A_SHOW_CHECKS, optShowChecks.getSelection());
store.setValue(CheckerPreferences.PREF_CHECKER_ERROR_FILTER_REGEX, optFilter.getText());
store.setValue(CheckerPreferences.PREF_CHECKER_VERBOSE, optVerbose.getSelection());
store.setValue(CheckerPreferences.PREF_CHECKER_JDK_PATH, optJDKPath.getText());
store.setValue(
CheckerPreferences.PREF_CHECKER_IMPLICIT_IMPORTS,
optImplicitImports.getSelection());
return true;
}
private void searchForClass() {
OpenTypeSelectionDialog dialog =
new OpenTypeSelectionDialog(
getShell(), true, null, null, IJavaSearchConstants.CLASS);
dialog.setTitle("Search for checker classes");
dialog.setMessage("Select additional checkers to use.");
if (dialog.open() == SelectionDialog.OK) {
Object[] results = dialog.getResult();
Set<String> classNames = new LinkedHashSet<String>();
for (Object result : results) {
if (result instanceof IType) {
IType type = (IType) result;
classNames.add(type.getFullyQualifiedName());
}
}
//TODO: CHECK FOR DUPLICATES?
final List<String> classesInTable = classesFromTableItems();
for (final String cn : classNames) {
final CheckerInfo ci = CheckerInfo.fromClassPath(cn, null);
if (!classesInTable.contains(
cn)) { //TODO: ADD A DIALOG TO WARN IF ALREADY CONTAINED
addProcTableItem(ci, false);
}
}
}
}
private void addOption() {
final InputDialog inputDialog =
new InputDialog(
getShell(),
"Add Option",
"Please enter your option exactly as it would appear on the command line. "
+ "Include any space that would appear between the option name and value.",
"",
null);
if (inputDialog.open() == SelectionDialog.OK) {
final String result = inputDialog.getValue();
addOptTableItem(new OptionLine(result, true));
}
}
private final List<String> classesFromTableItems() {
final List<String> classes = new ArrayList<String>(procTable.getItemCount());
for (final TableItem ti : procTable.getItems()) {
classes.add(ti.getText(CLASSES.ordinal()));
}
return classes;
}
private final List<String> customClassesFromTableItems() {
final List<String> classes = new ArrayList<String>(procTable.getItemCount());
for (final TableItem ti : procTable.getItems()) {
if (ti.getText(SOURCE.ordinal()).equals(CUSTOM_LABEL)) {
classes.add(ti.getText(CLASSES.ordinal()));
}
}
return classes;
}
private TableItem addProcTableItem(final CheckerInfo checkerInfo, boolean builtIn) {
final TableItem item = new TableItem(procTable, SWT.None);
item.setText(LABEL.ordinal(), checkerInfo.getLabel());
item.setText(SOURCE.ordinal(), (builtIn) ? BUILT_IN_LABEL : CUSTOM_LABEL);
item.setText(CLASSES.ordinal(), checkerInfo.getClassPath());
return item;
}
}