/* * Contributions to FindBugs * Copyright (C) 2008, Andrei Loskutov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package de.tobject.findbugs.properties; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ListViewer; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; 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.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabItem; import de.tobject.findbugs.FindbugsPlugin; import de.tobject.findbugs.builder.FindBugsWorker; import edu.umd.cs.findbugs.config.UserPreferences; /** * @author Andrei Loskutov */ public class FilterFilesTab extends Composite { private static IPath lastUsedPath; private final FindbugsPropertyPage propertyPage; private final FilterProvider filterIncl; private final FilterProvider filterExcl; private final FilterProvider filterExclBugs; private final class SelectionValidator { private final UserPreferences prefs; private final Collection<String> exclFiles; public SelectionValidator(FilterKind kind) { prefs = propertyPage.getCurrentUserPreferences(); exclFiles = kind.excludedPaths(prefs); } public IStatus validate(String path) { if (exclFiles.contains(path)) { return FindbugsPlugin.createErrorStatus("Filter selected in a conflicting list", null); } return FindbugsPlugin.createStatus(IStatus.OK, "", null); } } protected class FilterProvider extends SelectionAdapter implements IStructuredContentProvider { protected final List<PathElement> paths; private final FilterKind kind; private final Control control; private final ListViewer viewer; protected FilterProvider(ListViewer viewer, FilterKind kind) { this.paths = new ArrayList<PathElement>(); this.viewer = viewer; this.control = viewer.getList(); this.kind = kind; setFilters(propertyPage.getCurrentUserPreferences()); } void setFilters(UserPreferences prefs) { paths.clear(); paths.addAll(getFilterFiles(kind, prefs)); } @Override public void widgetSelected(SelectionEvent e) { addFiles(e.display.getActiveShell()); } public void addFiles(Shell parentShell) { FileDialog dialog = createFileDialog(parentShell); // The validator checks to see if the user's selection // is valid given the type of the object selected (e.g. // it can't be a folder) and the objects that have // already been selected String pathStr = openFileDialog(dialog); if (pathStr == null) { return; } addSelectedPaths(dialog); applyToPreferences(); validateAllFilters(); } private FileDialog createFileDialog(Shell parentShell) { FileDialog dialog = new FileDialog(parentShell, SWT.OPEN | SWT.MULTI); dialog.setFilterExtensions(new String[]{"*.xml"}); dialog.setText(getMessage(kind.propertyName) + ": select xml file(s) containing filters"); IPath lastUsed = getLastUsedPath(); String filterPath = null; if(lastUsed != null && lastUsed.toFile().isDirectory()){ filterPath = lastUsed.toOSString(); dialog.setFilterPath(filterPath); } return dialog; } protected String openFileDialog(FileDialog dialog) { return dialog.open(); } protected String[] getFileNames(FileDialog dialog) { return dialog.getFileNames(); } protected String getFilterPath(FileDialog dialog) { return dialog.getFilterPath(); } private void addSelectedPaths(FileDialog dialog) { String[] names = getFileNames(dialog); String filterPath = getFilterPath(dialog); for (String fileName : names) { IPath path = new Path(filterPath).append(fileName); PathElement pathElt = new PathElement(path, Status.OK_STATUS); if(!paths.contains(pathElt)) { paths.add(pathElt); } } } public void dispose() { // } public void inputChanged(Viewer viewer1, Object oldInput, Object newInput) { // } public Object[] getElements(Object inputElement) { return paths.toArray(); } boolean contains(Object o){ return paths.contains(o); } void setControlEnabled(boolean enabled){ control.setEnabled(enabled); } void refresh(){ validate(); viewer.setSelection(null); viewer.setInput(new Object()); viewer.refresh(true); } private void validate() { SelectionValidator validator = new SelectionValidator(kind); IStatus bad = null; IProject project = propertyPage.getProject(); for (PathElement path : paths) { String filterPath = FindBugsWorker.toFilterPath(path.getPath(), project).toOSString(); IStatus status = validator.validate(filterPath); path.setStatus(status); if(!status.isOK()){ bad = status; } } if(bad != null){ propertyPage.setErrorMessage(bad.getMessage()); } } public void remove(PathElement holder) { paths.remove(holder); applyToPreferences(); validateAllFilters(); } private void applyToPreferences() { validate(); kind.setPaths(propertyPage.getCurrentUserPreferences(), pathsToStrings(paths)); } } protected static final class PathElement { private final IPath path; private IStatus status; public PathElement(IPath path, IStatus status) { this.path = path; this.status = status; } public void setStatus(IStatus status) { this.status = status; } @Override public String toString() { return path.toString() + (status.isOK()? "" : " (" + status.getMessage() + ")"); } public String getPath() { return path.toOSString(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof PathElement) { return path.equals(((PathElement) obj).path); } return false; } @Override public int hashCode() { return path.hashCode(); } } /** * @param parent * @param style */ public FilterFilesTab(TabFolder parent, FindbugsPropertyPage page, int style) { super(parent, style); this.propertyPage = page; setLayout(new GridLayout(2, true)); TabItem tabDetector = new TabItem(parent, SWT.NONE); tabDetector.setText(getMessage("property.filterFilesTab")); tabDetector.setControl(this); tabDetector.setToolTipText("Configure external bug reporting filters"); filterIncl = createFilter(this, FilterKind.INCLUDE); filterExcl = createFilter(this, FilterKind.EXCLUDE); filterExclBugs = createFilter(this, FilterKind.EXCLUDE_BUGS); validateAllFilters(); } public void validateAllFilters() { propertyPage.setErrorMessage(null); filterIncl.refresh(); filterExcl.refresh(); filterExclBugs.refresh(); } public static void setLastUsedPath(IPath lastUsed) { // TODO write to preferences lastUsedPath = lastUsed; } public static IPath getLastUsedPath() { // TODO read from preferences return lastUsedPath; } /** * Helper method to shorten message access * @param key a message key * @return requested message */ protected String getMessage(String key) { return FindbugsPlugin.getDefault().getMessage(key); } private FilterProvider createFilter(final Composite parent, final FilterKind kind) { Composite tableComposite = new Composite(parent, SWT.NULL); GridLayout layout = new GridLayout(2, false); layout.marginHeight = 0; layout.marginWidth = 0; tableComposite.setLayout(layout); tableComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); Label titleLabel = new Label(tableComposite, SWT.NULL); final String title = getMessage(kind.propertyName); titleLabel.setText(title); titleLabel.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false, 2, 1)); final ListViewer viewer = new ListViewer(tableComposite, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); viewer.getControl().setLayoutData( new GridData(SWT.FILL, SWT.FILL, true, true, 1, 2)); final FilterProvider contentProvider = createFilterProvider(viewer, kind); viewer.setContentProvider(contentProvider); final Button addButton = new Button(tableComposite, SWT.PUSH); String addButtonLabel = getMessage(kind.propertyName + "addbutton"); addButton.setText(addButtonLabel); addButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false)); addButton.addSelectionListener(contentProvider); final Button removeButton = new Button(tableComposite, SWT.PUSH); removeButton.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, true)); String removeButtonLabel = getMessage(kind.propertyName + "removebutton"); removeButton.setText(removeButtonLabel); removeButton.setEnabled(false); removeButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Iterator<?> selectionIter = ((IStructuredSelection) viewer.getSelection()) .iterator(); while (selectionIter.hasNext()) { contentProvider.remove((PathElement) selectionIter.next()); } } }); viewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { removeButton.setEnabled(!event.getSelection().isEmpty()); } }); return contentProvider; } protected FilterProvider createFilterProvider(ListViewer viewer, FilterKind kind) { return new FilterProvider(viewer, kind); } private List<PathElement> getFilterFiles(FilterKind kind, UserPreferences prefs) { IProject project = propertyPage.getProject(); final List<PathElement> paths = new ArrayList<PathElement>(); Collection<String> filterPaths = kind.selectedPaths(prefs); if (filterPaths != null) { for (String path : filterPaths) { IPath filterPath = FindBugsWorker.getFilterPath(path, project); if(filterPath.toFile().exists()) { paths.add(new PathElement(filterPath, Status.OK_STATUS)); } } } return paths; } private Set<String> pathsToStrings(List<PathElement> paths) { IProject project = propertyPage.getProject(); Set<String>result = new LinkedHashSet<String>(); for (PathElement path : paths) { IPath filterPath = FindBugsWorker.toFilterPath(path.getPath(), project); result.add(filterPath.toOSString()); } return result; } protected enum FilterKind { INCLUDE("property.includefilter") { @Override Collection<String> selectedPaths(UserPreferences u) { return u.getIncludeFilterFiles(); } @Override Collection<String> excludedPaths(UserPreferences u) { Set<String> excl = new HashSet<String>(); excl.addAll(u.getExcludeFilterFiles()); excl.addAll(u.getExcludeBugsFiles()); return excl; } @Override void setPaths(UserPreferences u, Collection<String> files) { u.setIncludeFilterFiles(files); } }, EXCLUDE("property.excludefilter") { @Override Collection<String> selectedPaths(UserPreferences u) { return u.getExcludeFilterFiles(); } @Override Collection<String> excludedPaths(UserPreferences u) { Set<String> excl = new HashSet<String>(); excl.addAll(u.getIncludeFilterFiles()); excl.addAll(u.getExcludeBugsFiles()); return excl; } @Override void setPaths(UserPreferences u, Collection<String> files) { u.setExcludeFilterFiles(files); } }, EXCLUDE_BUGS("property.excludebugs") { @Override Collection<String> selectedPaths(UserPreferences u) { return u.getExcludeBugsFiles(); } @Override Collection<String> excludedPaths(UserPreferences u) { Set<String> excl = new HashSet<String>(); excl.addAll(u.getIncludeFilterFiles()); excl.addAll(u.getExcludeFilterFiles()); return excl; } @Override void setPaths(UserPreferences u, Collection<String> files) { u.setExcludeBugsFiles(files); } }; final String propertyName; FilterKind(String propertyName) { this.propertyName = propertyName; } abstract Collection<String> selectedPaths(UserPreferences u); abstract Collection<String> excludedPaths(UserPreferences u); abstract void setPaths(UserPreferences u, Collection<String> files); } @Override public void setEnabled(boolean enabled) { filterExcl.setControlEnabled(enabled); filterIncl.setControlEnabled(enabled); filterExclBugs.setControlEnabled(enabled); super.setEnabled(enabled); } void refreshUI(UserPreferences prefs) { filterExcl.setFilters(prefs); filterExclBugs.setFilters(prefs); filterIncl.setFilters(prefs); validateAllFilters(); } protected FilterProvider getFilterIncl() { return filterIncl; } protected FilterProvider getFilterExcl() { return filterExcl; } protected FilterProvider getFilterExclBugs() { return filterExclBugs; } }