/******************************************************************************* * Copyright (c) 2008 Vlad Dumitrescu and others. * 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: * Vlad Dumitrescu *******************************************************************************/ package org.erlide.dialyzer.ui.prefs; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.MultiRule; import org.eclipse.jface.dialogs.ControlEnableState; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; 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.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Table; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; import org.eclipse.ui.dialogs.PreferencesUtil; import org.eclipse.ui.dialogs.PropertyPage; import org.erlide.backend.BackendCore; import org.erlide.core.ErlangCore; import org.erlide.dialyzer.builder.DialyzerPreferences; import org.erlide.dialyzer.builder.DialyzerUtils; import org.erlide.dialyzer.builder.DialyzerUtils.DialyzerErrorException; import org.erlide.dialyzer.builder.ErlideDialyze; import org.erlide.engine.ErlangEngine; import org.erlide.engine.model.ErlElementKind; import org.erlide.engine.model.ErlModelException; import org.erlide.engine.model.IErlElement; import org.erlide.engine.model.root.IErlModel; import org.erlide.engine.model.root.IErlProject; import org.erlide.runtime.rpc.IOtpRpc; import org.erlide.runtime.rpc.RpcException; import org.erlide.ui.prefs.ProjectSelectionDialog; import org.erlide.util.ErlLogger; import org.osgi.service.prefs.BackingStoreException; import com.ericsson.otp.erlang.OtpErlangObject; import com.google.common.collect.Lists; import com.google.common.io.Files; public class DialyzerPreferencePage extends PropertyPage implements IWorkbenchPreferencePage { public class ContentProvider implements IStructuredContentProvider { @Override public void dispose() { } @Override public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) { } @Override public Object[] getElements(final Object inputElement) { return shownPLTFiles.toArray(); } } private static class LabelProvider implements ILabelProvider { @Override public void addListener(final ILabelProviderListener listener) { } @Override public void dispose() { } @Override public boolean isLabelProperty(final Object element, final String property) { return true; } @Override public void removeListener(final ILabelProviderListener listener) { } @Override public Image getImage(final Object element) { return null; } @Override public String getText(final Object element) { if (element instanceof String) { final String s = (String) element; return s; } return null; } } private static final int MAX_PLT_FILES = 256; DialyzerPreferences prefs; private IProject fProject; private Button fUseProjectSettings; private Link fChangeWorkspaceSettings; protected ControlEnableState fBlockEnableState; // private final Text pltEdit = null; private Combo fromCombo; private Button dialyzeCheckbox; private Composite prefsComposite; private CheckboxTableViewer fPLTTableViewer; private Button fAddButton; private Button fEditButton; private Button fRemoveButton; private Button fUpdatePLTButton; private Button noCheckPLTCheckbox; private Button removeWarningsOnCleanCheckbox; private final List<String> shownPLTFiles; public DialyzerPreferencePage() { super(); setTitle("Dialyzer options"); setDescription("Select the options for dialyzer."); shownPLTFiles = Lists.newArrayList(); } @Override protected Control createContents(final Composite parent) { loadPrefs(); prefsComposite = new Composite(parent, SWT.NONE); prefsComposite.setLayout(new GridLayout()); // final Group group = new Group(prefsComposite, SWT.NONE); final Composite group = prefsComposite;// new Composite(prefsComposite, // SWT.NONE); group.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); // group.setLayout(new GridLayout(1, false)); createDialyzeCheckbox(group); createPltSelection(group); createPltCheck(group); createFromSelection(group); createPltNoCheckbox(group); createRemoveWarningsOnCleanCheckbox(group); enableButtons(); if (isProjectPreferencePage()) { final boolean useProjectSettings = hasProjectSpecificOptions(fProject); enableProjectSpecificSettings(useProjectSettings); } performDefaults(); return prefsComposite; } private void createRemoveWarningsOnCleanCheckbox(final Composite group) { final Composite comp = new Composite(group, SWT.NONE); comp.setLayout(new GridLayout(2, false)); removeWarningsOnCleanCheckbox = new Button(comp, SWT.CHECK); removeWarningsOnCleanCheckbox.setText("Remove dialyzer warning on clean project"); new Label(comp, SWT.NONE); } private void createPltNoCheckbox(final Composite group) { final Composite comp = new Composite(group, SWT.NONE); comp.setLayout(new GridLayout(2, false)); noCheckPLTCheckbox = new Button(comp, SWT.CHECK); noCheckPLTCheckbox.setText("Do not check PLT on dialyzer run"); new Label(comp, SWT.NONE); } private void createPltCheck(final Composite group) { final Composite comp = new Composite(group, SWT.NONE); comp.setLayout(new GridLayout(2, false)); fUpdatePLTButton = new Button(comp, SWT.PUSH); fUpdatePLTButton.setText("Update PLT"); fUpdatePLTButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { checkSelectedPltFiles(); } }); final Label l = new Label(comp, SWT.NONE); l.setText("Warning: this can take some time"); } private void createDialyzeCheckbox(final Composite group) { final Composite comp = new Composite(group, SWT.NONE); // comp.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, // false)); comp.setLayout(new GridLayout(1, false)); dialyzeCheckbox = new Button(comp, SWT.CHECK); dialyzeCheckbox.setEnabled(false); dialyzeCheckbox.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { } }); dialyzeCheckbox.setText("Run dialyzer when compiling"); dialyzeCheckbox.setSelection(prefs.getDialyzeOnCompile()); } private void createFromSelection(final Composite group) { final Composite comp = new Composite(group, SWT.NONE); // comp.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, // false)); comp.setLayout(new GridLayout(2, false)); final Label l = new Label(comp, SWT.NONE); l.setText("Analyze from "); fromCombo = new Combo(comp, SWT.READ_ONLY); fromCombo.setItems(new String[] { "Source", "Binaries" }); fromCombo.setText(fromCombo.getItem(prefs.getFromSource() ? 0 : 1)); } private void createPltSelection(final Composite group) { final Composite composite = new Composite(group, SWT.NONE); composite.setLayout(new GridLayout(3, false)); GridData gd = new GridData(SWT.FILL, SWT.BEGINNING, true, false); composite.setLayoutData(gd); final Label l = new Label(composite, SWT.NONE); l.setText("PLT files (multiple PLT requires Erlang/OTP R14B01 or later)"); gd = new GridData(); gd.horizontalSpan = 2; l.setLayoutData(gd); new Label(composite, SWT.NONE); fPLTTableViewer = CheckboxTableViewer.newCheckList(composite, SWT.BORDER); fPLTTableViewer.setLabelProvider(new LabelProvider()); fPLTTableViewer.setContentProvider(new ContentProvider()); fPLTTableViewer.setInput(this); // fPLTList = new org.eclipse.swt.widgets.List(composite, SWT.MULTI // | SWT.V_SCROLL | SWT.BORDER); final Table table = fPLTTableViewer.getTable(); table. addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { enableButtons(); } }); gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); gd.horizontalSpan = 2; table.setLayoutData(gd); gd.heightHint = convertHeightInCharsToPixels(12); final Composite buttons = new Composite(composite, SWT.NULL); buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); final GridLayout layout = new GridLayout(); layout.marginHeight = 0; layout.marginWidth = 0; buttons.setLayout(layout); fAddButton = createButton(buttons, "Add...", new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { addPLTFile(); } }); fEditButton = createButton(buttons, "Change...", new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { changeSelectedPLTFiles(); } }); fRemoveButton = createButton(buttons, "Remove", new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { removeSelectedPLTFiles(); } }); if (isProjectPreferencePage()) { fAddButton.setVisible(false); fEditButton.setVisible(false); fRemoveButton.setVisible(false); } } private Button createButton(final Composite buttons, final String text, final SelectionListener selectionListener) { GridData gd; final Button button = new Button(buttons, SWT.PUSH); button.setText(text); gd = new GridData(); gd.horizontalAlignment = GridData.HORIZONTAL_ALIGN_FILL; button.setLayoutData(gd); button.addSelectionListener(selectionListener); return button; } protected boolean hasProjectSpecificOptions(final IProject project) { final DialyzerPreferences p = DialyzerPreferences.get(project); return p.hasOptionsAtLowestScope(); } private boolean isProjectPreferencePage() { return fProject != null; } @Override protected Label createDescriptionLabel(final Composite parent) { createProjectSpecificSettingsCheckBoxAndLink(parent); return super.createDescriptionLabel(parent); } protected void enableProjectSpecificSettings( final boolean useProjectSpecificSettings) { fUseProjectSettings.setSelection(useProjectSpecificSettings); enablePreferenceContent(useProjectSpecificSettings); fChangeWorkspaceSettings.setEnabled(!useProjectSpecificSettings); // doStatusChanged(); } private void enablePreferenceContent(final boolean useProjectSpecificSettings) { if (useProjectSpecificSettings) { if (fBlockEnableState != null) { fBlockEnableState.restore(); fBlockEnableState = null; } } else { if (fBlockEnableState == null) { fBlockEnableState = ControlEnableState.disable(prefsComposite); } } } private void createProjectSpecificSettingsCheckBoxAndLink(final Composite parent) { if (isProjectPreferencePage()) { final Composite composite = new Composite(parent, SWT.NONE); composite.setFont(parent.getFont()); final GridLayout layout = new GridLayout(2, false); layout.marginHeight = 0; layout.marginWidth = 0; composite.setLayout(layout); composite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); fUseProjectSettings = new Button(composite, SWT.CHECK); fUseProjectSettings.setText("Enable project specific settings"); fUseProjectSettings.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { final boolean sel = fUseProjectSettings.getSelection(); enableProjectSpecificSettings(sel); super.widgetSelected(e); } }); fChangeWorkspaceSettings = createLink(composite, "Configure Workspace settings..."); fChangeWorkspaceSettings .setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); final Label horizontalLine = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL); horizontalLine.setLayoutData( new GridData(GridData.FILL, GridData.FILL, true, false, 2, 1)); horizontalLine.setFont(composite.getFont()); } else { // if (supportsProjectSpecificOptions() && offerLink()) { fChangeWorkspaceSettings = createLink(parent, "Configure project specific settings.."); fChangeWorkspaceSettings .setLayoutData(new GridData(SWT.END, SWT.CENTER, true, false)); } } private Link createLink(final Composite composite, final String text) { final Link link = new Link(composite, SWT.NONE); link.setFont(composite.getFont()); link.setText("<A>" + text + "</A>"); //$NON-NLS-1$//$NON-NLS-2$ link.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { doLinkActivated((Link) e.widget); } }); return link; } void doLinkActivated(final Link widget) { if (isProjectPreferencePage()) { openWorkspacePreferences(null); } else { final List<IProject> erlProjects = new ArrayList<>(); final Set<IProject> projectsWithSpecifics = new HashSet<>(); final IErlModel model = ErlangEngine.getInstance().getModel(); try { for (final IErlProject ep : model.getErlangProjects()) { final IProject p = ep.getWorkspaceProject(); if (hasProjectSpecificOptions(p)) { projectsWithSpecifics.add(p); } erlProjects.add(p); } } catch (final ErlModelException e) { } final ProjectSelectionDialog dialog = new ProjectSelectionDialog(getShell(), erlProjects, projectsWithSpecifics); if (dialog.open() == Window.OK) { final IProject res = (IProject) dialog.getFirstResult(); openProjectProperties(res); } } } protected void enableButtons() { final IStructuredSelection selection = (IStructuredSelection) fPLTTableViewer .getSelection(); final int selectionCount = selection.size(); fEditButton.setEnabled(selectionCount == 1); fRemoveButton.setEnabled(selectionCount > 0); fUpdatePLTButton.setEnabled(selectionCount > 0); fAddButton.setEnabled(shownPLTFiles.size() < MAX_PLT_FILES); } private void openProjectProperties(final IProject project) { final String id = getPropertyPageID(); if (id != null) { PreferencesUtil.createPropertyDialogOn(getShell(), project, id, new String[] { id }, null).open(); } } protected final void openWorkspacePreferences(final Object data) { final String id = getPreferencePageID(); PreferencesUtil .createPreferenceDialogOn(getShell(), id, new String[] { id }, data) .open(); } protected static String getPreferencePageID() { return "org.erlide.ui.preferences.dialyzer"; } protected static String getPropertyPageID() { return "org.erlide.ui.properties.dialyzerPreferencePage"; } boolean optionsAreOk() { for (final String s : shownPLTFiles) { final File f = new File(s); if (!f.exists()) { return false; } } return true; } @Override public boolean performOk() { try { if (fUseProjectSettings != null && !fUseProjectSettings.getSelection() && isProjectPreferencePage()) { prefs.removeAllProjectSpecificSettings(); } else { if (fPLTTableViewer != null) { prefs.setPltPaths(shownPLTFiles); prefs.setEnabledPltPaths(getCheckedPltFiles()); } prefs.setFromSource(fromCombo.getSelectionIndex() == 0); prefs.setDialyzeOnCompile(dialyzeCheckbox.getSelection()); prefs.setNoCheckPLT(noCheckPLTCheckbox.getSelection()); prefs.setRemoveWarningsOnClean( removeWarningsOnCleanCheckbox.getSelection()); prefs.store(); } } catch (final BackingStoreException e) { ErlLogger.warn(e); } return super.performOk(); } @Override protected void performDefaults() { loadPrefs(); shownPLTFiles.clear(); shownPLTFiles.addAll(prefs.getPltPaths()); if (fPLTTableViewer != null) { fPLTTableViewer.refresh(); fPLTTableViewer.setAllChecked(false); for (final String s : prefs.getEnabledPltPaths()) { fPLTTableViewer.setChecked(s, true); } } if (fromCombo != null) { fromCombo.setText(fromCombo.getItem(prefs.getFromSource() ? 0 : 1)); } if (dialyzeCheckbox != null) { // dialyzeCheckbox.setSelection(prefs.getDialyzeOnCompile()); dialyzeCheckbox.setSelection(false); } if (noCheckPLTCheckbox != null) { noCheckPLTCheckbox.setSelection(prefs.getNoCheckPLT()); } if (removeWarningsOnCleanCheckbox != null) { removeWarningsOnCleanCheckbox.setSelection(prefs.getRemoveWarningsOnClean()); } super.performDefaults(); } private void loadPrefs() { try { prefs = DialyzerPreferences.get(fProject); } catch (final Exception e) { setErrorMessage("Error loading preferences: " + e.getMessage()); ErlLogger.error(e); } } @Override public void setElement(final IAdaptable element) { fProject = (IProject) element.getAdapter(IResource.class); super.setElement(element); } @Override public void init(final IWorkbench workbench) { } private String selectPLTDialog(final String s) { final FileDialog dialog = new FileDialog(getShell(), SWT.SINGLE); dialog.setText("Select PLT file"); dialog.setFileName(s); dialog.setFilterPath(s); dialog.setFilterNames(new String[] { "Dialyzer PLT file (*.plt)", "Any File" }); dialog.setFilterExtensions(new String[] { "*.plt", "*.*" }); final String result = dialog.open(); return result; } private void changeSelectedPLTFiles() { final IStructuredSelection selection = (IStructuredSelection) fPLTTableViewer .getSelection(); if (selection.size() != 1) { return; } final Object selectedElement = selection.getFirstElement(); final int i = shownPLTFiles.indexOf(selectedElement); if (i == -1) { return; } final String result = selectPLTDialog((String) selectedElement); if (result == null) { return; } shownPLTFiles.set(i, result); fPLTTableViewer.refresh(); } protected void removeSelectedPLTFiles() { final IStructuredSelection selection = (IStructuredSelection) fPLTTableViewer .getSelection(); for (final Object o : selection.toList()) { shownPLTFiles.remove(o); } fPLTTableViewer.refresh(); } protected void addPLTFile() { final String result = selectPLTDialog(null); if (result == null) { return; } shownPLTFiles.add(result); fPLTTableViewer.refresh(); } protected void checkSelectedPltFiles() { final Job job = new UpdateDialyzerPLTFileOperation("Checking PLT file", getSelectedPltFiles(), getCheckedPltFiles()); final ISchedulingRule rule = fProject; job.setRule(rule); job.setUser(true); job.setSystem(false); job.schedule(); } private List<String> getCheckedPltFiles() { final List<String> l = Lists.newArrayList(); for (final Object o : fPLTTableViewer.getCheckedElements()) { l.add((String) o); } return l; } private List<String> getSelectedPltFiles() { final IStructuredSelection selection = (IStructuredSelection) fPLTTableViewer .getSelection(); final List<String> result = Lists.newArrayListWithCapacity(selection.size()); for (final Object o : selection.toList()) { final String s = (String) o; result.add(s); } return result; } private final class UpdateDialyzerPLTFileOperation extends Job { private final List<String> selectedPLTPaths, checkedPltPaths; public UpdateDialyzerPLTFileOperation(final String name, final List<String> selectedPLTPaths, final List<String> checkedPltPaths) { super(name); this.selectedPLTPaths = selectedPLTPaths; this.checkedPltPaths = checkedPltPaths; } IStatus newErrorStatus(final Throwable throwable) { return new Status(IStatus.ERROR, ErlangCore.PLUGIN_ID, throwable.getMessage()); } @Override protected IStatus run(final IProgressMonitor monitor) { final IProject project = fProject; if (project != null) { try { final String alternatePltFileDirectory = DialyzerPreferences .getAlternatePLTFileDirectoryFromPreferences(); checkIfPltFilesShouldBeCopied(alternatePltFileDirectory); final IErlProject eproject = ErlangEngine.getInstance().getModel() .findProject(project); if (eproject != null) { final IOtpRpc backend = BackendCore.getBuildBackend(eproject); for (final String pltPath : selectedPLTPaths) { checkPlt(pltPath, alternatePltFileDirectory, monitor, backend); } } } catch (final Exception e) { return newErrorStatus(e); } finally { monitor.done(); } } return Status.OK_STATUS; } private void checkIfPltFilesShouldBeCopied(final String alternatePltFileDirectory) throws IOException { if (alternatePltFileDirectory == null) { return; } final List<String> selected = Lists.newArrayList(selectedPLTPaths); boolean changed = false; for (final String pltPath : selected) { final File f = new File(pltPath); if (!f.canWrite()) { final String newPath = copyPltFile(pltPath, alternatePltFileDirectory); selectedPLTPaths.remove(pltPath); selectedPLTPaths.remove(newPath); shownPLTFiles.remove(newPath); shownPLTFiles.add(newPath); selectedPLTPaths.add(newPath); checkedPltPaths.remove(newPath); if (checkedPltPaths.remove(pltPath)) { checkedPltPaths.add(newPath); } changed = true; } } if (changed) { getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!fPLTTableViewer.getControl().isDisposed()) { fPLTTableViewer.refresh(); fPLTTableViewer.setSelection( new StructuredSelection(selectedPLTPaths)); fPLTTableViewer.setCheckedElements(checkedPltPaths.toArray()); } } }); } } private String copyPltFile(final String pltPath, final String alternatePltFileDirectory) throws IOException { IPath path = new Path(pltPath); final String name = path.lastSegment(); path = new Path(alternatePltFileDirectory).append(name); Files.copy(new File(pltPath), new File(path.toOSString())); return path.toPortableString(); } private void checkPlt(final String pltPath, final String alternatePltFileDirectory, final IProgressMonitor monitor, final IOtpRpc backend) throws DialyzerErrorException, ErlModelException, RpcException { try { monitor.subTask("Checking PLT file " + pltPath); List<String> ebinDirs = null; if (alternatePltFileDirectory != null) { ebinDirs = Lists.newArrayList(); for (final IErlElement i : ErlangEngine.getInstance().getModel() .getChildrenOfKind(ErlElementKind.PROJECT)) { final IErlProject project = (IErlProject) i; final String ebinDir = project.getWorkspaceProject() .getFolder(project.getProperties().getOutputDir()) .getLocation().toString(); ebinDirs.add(ebinDir); } } final OtpErlangObject result = ErlideDialyze.checkPlt(backend, pltPath, ebinDirs); DialyzerUtils.checkDialyzeError(result); } finally { monitor.worked(1); } } } public ISchedulingRule createRule(final Set<IProject> projects) { ISchedulingRule combinedRule = null; for (final IProject project : projects) { combinedRule = MultiRule.combine(project, combinedRule); } return combinedRule; } }