/* * FindBugs Eclipse Plug-in. * Copyright (C) 2003 - 2004, Peter Friese * Copyright (C) 2004-2005, University of Maryland * * 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; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.CheckForNull; import org.dom4j.DocumentException; import org.eclipse.core.internal.preferences.EclipsePreferences; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; 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.Platform; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.preference.IPersistentPreferenceStore; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.eclipse.ui.preferences.ScopedPreferenceStore; import org.osgi.framework.BundleContext; import de.tobject.findbugs.builder.FindBugsBuilder; import de.tobject.findbugs.builder.FindBugsWorker; import de.tobject.findbugs.io.FileOutput; import de.tobject.findbugs.io.IO; import de.tobject.findbugs.marker.FindBugsMarker; import de.tobject.findbugs.nature.FindBugsNature; import de.tobject.findbugs.preferences.FindBugsConstants; import de.tobject.findbugs.preferences.FindBugsPreferenceInitializer; import de.tobject.findbugs.properties.DetectorProvider; import de.tobject.findbugs.properties.DetectorValidator; import de.tobject.findbugs.properties.DetectorValidator.ValidationStatus; import de.tobject.findbugs.reporter.Reporter; import de.tobject.findbugs.view.IMarkerSelectionHandler; import de.tobject.findbugs.view.explorer.BugContentProvider; import edu.umd.cs.findbugs.BugCode; import edu.umd.cs.findbugs.BugPattern; import edu.umd.cs.findbugs.DetectorFactoryCollection; import edu.umd.cs.findbugs.FindBugs; import edu.umd.cs.findbugs.Plugin; import edu.umd.cs.findbugs.PluginException; import edu.umd.cs.findbugs.Project; import edu.umd.cs.findbugs.SortedBugCollection; import edu.umd.cs.findbugs.Version; import edu.umd.cs.findbugs.config.UserPreferences; import edu.umd.cs.findbugs.plugin.eclipse.quickfix.BugResolutionAssociations; import edu.umd.cs.findbugs.plugin.eclipse.quickfix.BugResolutionLoader; import edu.umd.cs.findbugs.plugins.DuplicatePluginIdException; /** * The main plugin class to be used in the desktop. */ public class FindbugsPlugin extends AbstractUIPlugin { /** * The plug-in identifier of the FindBugs Plug-in (value * "edu.umd.cs.findbugs.plugin.eclipse", was * <code>"de.tobject.findbugs"</code>). */ public static final String PLUGIN_ID = "edu.umd.cs.findbugs.plugin.eclipse"; //$NON-NLS-1$ private static final String DEFAULT_CLOUD_ID = "edu.umd.cs.findbugs.cloud.doNothingCloud"; public static final String ICON_PATH = "icons/"; public static final String ICON_DEFAULT = "buggy-tiny-gray.png"; @SuppressWarnings("restriction") private static final IPath WORKSPACE_PREFS_PATH = Platform.getStateLocation(Platform.getBundle(Platform.PI_RUNTIME)) .append(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME) .append(PLUGIN_ID + "." + EclipsePreferences.PREFS_FILE_EXTENSION); @java.lang.SuppressWarnings("restriction") public static final IPath DEFAULT_PREFS_PATH = new Path(EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME) .append("edu.umd.cs.findbugs.core.prefs"); public static final IPath DEPRECATED_PREFS_PATH = new Path(".fbprefs"); public static final String DETAILS_VIEW_ID = "de.tobject.findbugs.view.buginfoview"; public static final String USER_ANNOTATIONS_VIEW_ID = "de.tobject.findbugs.view.userannotationsview"; public static final String TREE_VIEW_ID = "de.tobject.findbugs.view.bugtreeview"; public static final String BUG_CONTENT_PROVIDER_ID = "de.tobject.findbugs.view.explorer.BugContentProvider"; /** Map containing preloaded ImageDescriptors */ private final Map<String, ImageDescriptor> imageDescriptors = new HashMap<String, ImageDescriptor>(13); /** Controls debugging of the plugin */ public static boolean DEBUG =true; /** * The identifier for the FindBugs builder (value * <code>"edu.umd.cs.findbugs.plugin.eclipse.findbugsbuilder"</code>). */ public static final String BUILDER_ID = PLUGIN_ID + ".findbugsBuilder"; //$NON-NLS-1$ /** * The identifier for the FindBugs nature (value * <code>"edu.umd.cs.findbugs.plugin.eclipse.findbugsnature"</code>). * * @see org.eclipse.core.resources.IProject#hasNature(java.lang.String) */ public static final String NATURE_ID = PLUGIN_ID + ".findbugsNature"; //$NON-NLS-1$ // Debugging options private static final String PLUGIN_DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$ private static final String BUILDER_DEBUG = PLUGIN_ID + "/debug/builder"; //$NON-NLS-1$ private static final String NATURE_DEBUG = PLUGIN_ID + "/debug/nature"; //$NON-NLS-1$ private static final String REPORTER_DEBUG = PLUGIN_ID + "/debug/reporter"; //$NON-NLS-1$ private static final String CONTENT_DEBUG = PLUGIN_ID + "/debug/content"; //$NON-NLS-1$ private static final String PROFILER_DEBUG = PLUGIN_ID + "/debug/profiler"; //$NON-NLS-1$ // Persistent and session property keys public static final QualifiedName SESSION_PROPERTY_BUG_COLLECTION = new QualifiedName(FindbugsPlugin.PLUGIN_ID + ".sessionprops", "bugcollection"); public static final QualifiedName SESSION_PROPERTY_BUG_COLLECTION_DIRTY = new QualifiedName(FindbugsPlugin.PLUGIN_ID + ".sessionprops", "bugcollection.dirty"); public static final QualifiedName SESSION_PROPERTY_USERPREFS = new QualifiedName(FindbugsPlugin.PLUGIN_ID + ".sessionprops", "userprefs"); public static final QualifiedName SESSION_PROPERTY_SETTINGS_ON = new QualifiedName( FindbugsPlugin.PLUGIN_ID + ".sessionprops", "settingsOn"); public static final String LIST_DELIMITER = ";"; //$NON-NLS-1$ /** The shared instance. */ private static FindbugsPlugin plugin; private static boolean customDetectorsInitialized; /** Resource bundle. */ private ResourceBundle resourceBundle; private BugResolutionAssociations bugResolutions; private boolean bugResolutionsLoaded; /** * Constructor. */ public FindbugsPlugin() { plugin = this; } @Override public void start(BundleContext context) throws Exception { super.start(context); Version.registerApplication("FindBugs-Eclipse", Version.RELEASE); // configure debugging configurePluginDebugOptions(); // initialize resource strings try { resourceBundle = ResourceBundle.getBundle("de.tobject.findbugs.messages"); //this is correct //$NON-NLS-1$ } catch (MissingResourceException x) { resourceBundle = null; } if (System.getProperty("findbugs.home") == null) { // TODO workaround for findbugs home property // - see de.tobject.findbugs.builder.FindBugsWorker.work() too String findBugsHome = getFindBugsEnginePluginLocation(); if (DEBUG) { logInfo("FindBugs home is: " + findBugsHome); } System.setProperty("findbugs.home", findBugsHome); } if (System.getProperty("findbugs.cloud.default") == null) { // TODO workaround for findbugs default cloud property // - see edu.umd.cs.findbugs.cloud.CloudFactory and messages.xml String defCloud = DEFAULT_CLOUD_ID; if (DEBUG) { logInfo("Using default cloud: " + defCloud); } System.setProperty("findbugs.cloud.default", defCloud); } /** Don't load main classes */ FindBugs.setNoMains(); // Register our save participant FindbugsSaveParticipant saveParticipant = new FindbugsSaveParticipant(); ResourcesPlugin.getWorkspace().addSaveParticipant(plugin, saveParticipant); } public static void dumpClassLoader(Class<?> c) { System.out.printf("Class loaders for %s:%n", c.getName()); ClassLoader loader = c.getClassLoader(); while (loader != null) { System.out.printf(" %s %s%n", loader.toString(), loader.getClass().getSimpleName()); loader = loader.getParent(); } } /** * The complexity of the code below is partly caused by the fact that we * might have multiple ways to install and/or enable custom plugins. There * are plugins discovered by FB itself, plugins contributed to Eclipse and * plugins added by user manually via properties. Plugins can be disabled * via code or properties. The code below is still work in progress, see * also {@link DetectorProvider#getPluginElements(UserPreferences)}. * * @param detectorPaths * list of possible detector plugins * @param force * true if we MUST set plugins even if the given list is empty */ public static synchronized void applyCustomDetectors(boolean force) { if(customDetectorsInitialized && !force) { return; } customDetectorsInitialized = true; DetectorValidator validator = new DetectorValidator(); final SortedSet<String> detectorPaths = new TreeSet<String>(); SortedMap<String, String> contributedDetectors = DetectorsExtensionHelper.getContributedDetectors(); UserPreferences corePreferences = getCorePreferences(null, force); detectorPaths.addAll(corePreferences.getCustomPlugins(true)); if(DEBUG) { dumpClassLoader(FindbugsPlugin.class); dumpClassLoader(Plugin.class); System.out.println("applyCustomDetectors - going to add " + detectorPaths.size() + " plugin urls..."); for (String url : detectorPaths) { System.out.println("\t" + url); } } // disable custom plugins configured via properties, if they are already loaded Set<String> disabledPlugins = corePreferences.getCustomPlugins(false); Map<URI, Plugin> allPlugins = Plugin.getAllPluginsMap(); for (Entry<URI, Plugin> entry : allPlugins.entrySet()) { Plugin fbPlugin = entry.getValue(); String pluginId = fbPlugin.getPluginId(); // ignore all custom plugins with the same plugin id as already loaded if(contributedDetectors.containsKey(pluginId)) { contributedDetectors.remove(pluginId); detectorPaths.remove(pluginId); } if (fbPlugin.isCorePlugin() || fbPlugin.isInitialPlugin()) { continue; } if (disabledPlugins.contains(entry.getKey().getPath()) || disabledPlugins.contains(pluginId)) { fbPlugin.setGloballyEnabled(false); Plugin.removeCustomPlugin(fbPlugin); if (DEBUG) { System.out.println("Removed plugin: " + fbPlugin + " loaded from " + entry.getKey()); } } } HashSet<Plugin> enabled = new HashSet<Plugin>(); // adding FindBugs *Eclipse* plugins, key plugin id, value is path for (Entry<String, String> entry : contributedDetectors.entrySet()) { String pluginId = entry.getKey(); String pluginPath = entry.getValue(); URI uri = new File(pluginPath).toURI(); if (disabledPlugins.contains(pluginId) || disabledPlugins.contains(pluginPath) || allPlugins.containsKey(uri)) { continue; } addCustomPlugin(enabled, uri); } // adding custom plugins configured via properties, but only if they are not loaded yet for (String path : detectorPaths) { // this is plugin id, so we can't use it as URL if(new Path(path).segmentCount() == 1) { continue; } path = FindBugsWorker.getFilterPath(path, null).toOSString(); URI uri = new File(path).toURI(); if(allPlugins.containsKey(uri)) { continue; } ValidationStatus status = validator.validate(path); if (status.isOK()) { addCustomPlugin(enabled, uri); } else { getDefault().getLog().log(status); } } if (DEBUG) { System.out.println("applyCustomDetectors - there was " + detectorPaths.size() + " extra FB plugin urls with " + enabled.size() + " valid FB plugins and " + allPlugins.size() + " total plugins registered by FB."); for (Entry<URI, Plugin> entry : allPlugins.entrySet()) { Plugin fbPlugin = entry.getValue(); if (fbPlugin.isGloballyEnabled()) { System.out.println("IS enabled:\t" + fbPlugin.getPluginId()); } else { System.out.println("NOT enabled:\t" + fbPlugin.getPluginId()); } } } } protected static void addCustomPlugin(HashSet<Plugin> enabled, URI uri) { try { // bug 3117769 - we must provide our own classloader // to allow third-party plugins extend the classpath via // "Buddy" classloading // see also: Eclipse-BuddyPolicy attribute in MANIFEST.MF Plugin fbPlugin = Plugin.addCustomPlugin(uri, FindbugsPlugin.class.getClassLoader()); if(fbPlugin != null) { // TODO line below required to enable this *optional* plugin // but it should be taken by FB core from the findbugs.xml, // which currently only works for *core* plugins only fbPlugin.setGloballyEnabled(true); enabled.add(fbPlugin); } } catch (PluginException e) { getDefault().logException(e, "Failed to load plugin for custom detector: " + uri); } catch (DuplicatePluginIdException e) { getDefault().logException(e, e.getPluginId() + " already loaded from " + e.getPreviouslyLoadedFrom() + ", ignoring: " + uri); } } @Override protected void initializeImageRegistry(ImageRegistry reg) { for (FindBugsMarker.MarkerRank prio : FindBugsMarker.MarkerRank.values()) { String iconName = prio.iconName(); registerIcon(reg, iconName); } for (FindBugsMarker.MarkerConfidence prio : FindBugsMarker.MarkerConfidence.values()) { String iconName = prio.iconName(); registerIcon(reg, iconName); } registerIcon(reg, ICON_DEFAULT); } private void registerIcon(ImageRegistry reg, String iconName) { ImageDescriptor descriptor = getImageDescriptor(iconName); if (descriptor != null) { reg.put(iconName, descriptor); } } /** * Returns the shared instance. */ public static FindbugsPlugin getDefault() { return plugin; } /** * @return active window instance, never null */ public static IWorkbenchWindow getActiveWorkbenchWindow() { if (Display.getCurrent() != null) { return getDefault().getWorkbench().getActiveWorkbenchWindow(); } // need to call from UI thread final IWorkbenchWindow[] window = new IWorkbenchWindow[1]; Display.getDefault().syncExec(new Runnable() { public void run() { window[0] = getDefault().getWorkbench().getActiveWorkbenchWindow(); } }); return window[0]; } /** * Returns the SWT Shell of the active workbench window or <code>null</code> * if no workbench window is active. * * @return the SWT Shell of the active workbench window, or * <code>null</code> if no workbench window is active */ public static Shell getShell() { IWorkbenchWindow window = getActiveWorkbenchWindow(); if (window == null) { return null; } return window.getShell(); } /** * Returns the string from the plugin's resource bundle, or 'key' if not * found. */ public static String getResourceString(String key) { ResourceBundle bundle = FindbugsPlugin.getDefault().getResourceBundle(); try { return bundle.getString(key); } catch (MissingResourceException e) { return key; } } /** * Returns the plugin's resource bundle, */ public ResourceBundle getResourceBundle() { return resourceBundle; } public void configurePluginDebugOptions() { if (isDebugging()) { // debugging for the plugin itself String option = Platform.getDebugOption(PLUGIN_DEBUG); FindbugsPlugin.DEBUG = Boolean.valueOf(option).booleanValue(); // debugging for the builder and friends option = Platform.getDebugOption(BUILDER_DEBUG); FindBugsBuilder.DEBUG = Boolean.valueOf(option).booleanValue(); FindBugsWorker.DEBUG = FindBugsBuilder.DEBUG; // debugging for the nature option = Platform.getDebugOption(NATURE_DEBUG); FindBugsNature.DEBUG = Boolean.valueOf(option).booleanValue(); // debugging for the reporter option = Platform.getDebugOption(REPORTER_DEBUG); Reporter.DEBUG = Boolean.valueOf(option).booleanValue(); // debugging for the content provider option = Platform.getDebugOption(CONTENT_DEBUG); BugContentProvider.DEBUG = Boolean.valueOf(option).booleanValue(); option = Platform.getDebugOption(PROFILER_DEBUG); if (Boolean.valueOf(option).booleanValue()) { System.setProperty("profiler.report", "true"); } } } /** * Find the filesystem path of the FindBugs plugin directory. * * @return the filesystem path of the FindBugs plugin directory, or null if * the FindBugs plugin directory cannot be found */ public static String getFindBugsEnginePluginLocation() { // findbugs.home should be set to the directory the plugin is // installed in. URL u = plugin.getBundle().getEntry("/"); try { URL bundleRoot = FileLocator.resolve(u); String path = bundleRoot.getPath(); if (FindBugsBuilder.DEBUG) { System.out.println("Pluginpath: " + path); //$NON-NLS-1$ } if (path.endsWith("fb-trunk/eclipsePlugin/")) { File f = new File(path); f = f.getParentFile(); f = new File(f, "findbugs"); path = f.getPath() + "/"; } return path; } catch (IOException e) { FindbugsPlugin.getDefault().logException(e, "IO Exception locating engine plugin"); } return null; } /** * @param key * @return */ public String getMessage(String key) { return getResourceString(key); } /** * Log an exception. * * @param e * the exception * @param message * message describing how/why the exception occurred */ public void logException(Throwable e, String message) { logMessage(IStatus.ERROR, message, e); } /** * Log an error. * * @param message * error message */ public void logError(String message) { logMessage(IStatus.ERROR, message, null); } /** * Log a warning. * * @param message * warning message */ public void logWarning(String message) { logMessage(IStatus.WARNING, message, null); } /** * Log an informational message. * * @param message * the informational message */ public void logInfo(String message) { logMessage(IStatus.INFO, message, null); } public void logMessage(int severity, String message, Throwable e) { if (DEBUG) { String what = (severity == IStatus.ERROR) ? (e != null ? "Exception" : "Error") : "Warning"; System.out.println(what + " in FindBugs plugin: " + message); if (e != null) { e.printStackTrace(); } } IStatus status = createStatus(severity, message, e); getLog().log(status); } public static IStatus createStatus(int severity, String message, Throwable e) { return new Status(severity, FindbugsPlugin.PLUGIN_ID, 0, message, e); } public static IStatus createErrorStatus(String message, Throwable e) { return new Status(IStatus.ERROR, FindbugsPlugin.PLUGIN_ID, 0, message, e); } /** * Get the file resource used to store findbugs warnings for a project. * * @param project * the project * @return the IPath to the file (which may not actually exist in the * filesystem yet) */ public static IPath getBugCollectionFile(IProject project) { // IPath path = project.getWorkingLocation(PLUGIN_ID); // // project-specific but not user-specific? IPath path = getDefault().getStateLocation(); // user-specific but not // project-specific return path.append(project.getName() + ".fbwarnings"); } private static boolean isBugCollectionDirty(IProject project) throws CoreException { Object dirty = project.getSessionProperty(SESSION_PROPERTY_BUG_COLLECTION_DIRTY); if (dirty == null) { return false; } return ((Boolean) dirty).booleanValue(); } public static void markBugCollectionDirty(IProject project, boolean isDirty) throws CoreException { project.setSessionProperty(SESSION_PROPERTY_BUG_COLLECTION_DIRTY, isDirty ? Boolean.TRUE : Boolean.FALSE); } @CheckForNull public static SortedBugCollection getBugCollectionIfSet(IProject project) { try { return (SortedBugCollection) project.getSessionProperty(SESSION_PROPERTY_BUG_COLLECTION); } catch (CoreException ignored) { FindbugsPlugin.getDefault().logException(ignored, "IO Exception reading project bugs."); return null; } } public static SortedBugCollection getBugCollection(IProject project, IProgressMonitor monitor) throws CoreException { return getBugCollection(project, monitor, true); } /** * Get the stored BugCollection for project. If there is no stored bug * collection for the project, or if an error occurs reading the stored bug * collection, a default empty collection is created and returned. * * @param project * the eclipse project * @param monitor * a progress monitor * @return the stored BugCollection, never null * @throws CoreException */ public static SortedBugCollection getBugCollection(IProject project, IProgressMonitor monitor, boolean useCloud) throws CoreException { SortedBugCollection bugCollection = (SortedBugCollection) project.getSessionProperty(SESSION_PROPERTY_BUG_COLLECTION); if (bugCollection == null) { try { readBugCollectionAndProject(project, monitor, useCloud); bugCollection = (SortedBugCollection) project.getSessionProperty(SESSION_PROPERTY_BUG_COLLECTION); } catch (IOException e) { FindbugsPlugin.getDefault().logException(e, "Could not read bug collection for project"); bugCollection = createDefaultEmptyBugCollection(project); } catch (DocumentException e) { FindbugsPlugin.getDefault().logException(e, "Could not read bug collection for project"); bugCollection = createDefaultEmptyBugCollection(project); } } return bugCollection; } private static void cacheBugCollectionAndProject(IProject project, SortedBugCollection bugCollection, Project fbProject) throws CoreException { project.setSessionProperty(SESSION_PROPERTY_BUG_COLLECTION, bugCollection); markBugCollectionDirty(project, false); } private static SortedBugCollection createDefaultEmptyBugCollection(IProject project) throws CoreException { SortedBugCollection bugCollection = new SortedBugCollection(); Project fbProject = bugCollection.getProject(); UserPreferences userPrefs = getUserPreferences(project); String cloudId = userPrefs.getCloudId(); if (cloudId != null) { fbProject.setCloudId(cloudId); } cacheBugCollectionAndProject(project, bugCollection, fbProject); return bugCollection; } /** * Read saved bug collection and findbugs project from file. Will populate * the bug collection and findbugs project session properties if successful. * If there is no saved bug collection and project for the eclipse project, * then FileNotFoundException will be thrown. * * @param project * the eclipse project * @param monitor * a progress monitor * @throws java.io.FileNotFoundException * the saved bug collection doesn't exist * @throws IOException * @throws DocumentException * @throws CoreException */ private static void readBugCollectionAndProject(IProject project, IProgressMonitor monitor, boolean useCloud) throws IOException, DocumentException, CoreException { SortedBugCollection bugCollection; IPath bugCollectionPath = getBugCollectionFile(project); // Don't turn the path to an IFile because it isn't local to the // project. // see the javadoc for org.eclipse.core.runtime.Plugin File bugCollectionFile = bugCollectionPath.toFile(); if (!bugCollectionFile.exists()) { // throw new // FileNotFoundException(bugCollectionFile.getLocation().toOSString()); getDefault().logInfo("creating new bug collection: " + bugCollectionPath.toOSString()); createDefaultEmptyBugCollection(project); // since we no longer // throw, have to do this // here return; } UserPreferences prefs = getUserPreferences(project); bugCollection = new SortedBugCollection(); bugCollection.getProject().setGuiCallback(new EclipseGuiCallback(project)); bugCollection.setDoNotUseCloud(!useCloud); bugCollection.readXML(bugCollectionFile); if (useCloud) { String cloudId = prefs.getCloudId(); if (cloudId != null) { bugCollection.getProject().setCloudId(cloudId); } } cacheBugCollectionAndProject(project, bugCollection, bugCollection.getProject()); } /** * Store a new bug collection for a project. The collection is stored in the * session, and also in a file in the project. * * @param project * the project * @param bugCollection * the bug collection * @param monitor * progress monitor * @throws IOException * @throws CoreException */ public static void storeBugCollection(IProject project, final SortedBugCollection bugCollection, IProgressMonitor monitor) throws IOException, CoreException { // Store the bug collection and findbugs project in the session project.setSessionProperty(SESSION_PROPERTY_BUG_COLLECTION, bugCollection); if (bugCollection != null) { writeBugCollection(project, bugCollection, monitor); } } /** * If necessary, save current bug collection for project to disk. * * @param project * the project * @param monitor * a progress monitor * @throws CoreException */ public static void saveCurrentBugCollection(IProject project, IProgressMonitor monitor) throws CoreException { if (isBugCollectionDirty(project)) { SortedBugCollection bugCollection = (SortedBugCollection) project.getSessionProperty(SESSION_PROPERTY_BUG_COLLECTION); if (bugCollection != null) { writeBugCollection(project, bugCollection, monitor); } } } private static void writeBugCollection(IProject project, final SortedBugCollection bugCollection, IProgressMonitor monitor) throws CoreException { // Save to file IPath bugCollectionPath = getBugCollectionFile(project); // Don't turn the path to an IFile because it isn't local to the // project. // see the javadoc for org.eclipse.core.runtime.Plugin File bugCollectionFile = bugCollectionPath.toFile(); FileOutput fileOutput = new FileOutput() { public void writeFile(OutputStream os) throws IOException { bugCollection.writeXML(os); } public String getTaskDescription() { return "creating XML FindBugs data file"; } }; IO.writeFile(bugCollectionFile, fileOutput, monitor); markBugCollectionDirty(project, false); } /** * Get the FindBugs preferences file for a project (which may not exist yet) * * @param project * the project * @return the IFile for the FindBugs preferences file, if any. Can be * "empty" handle if the real file does not exist yet */ private static IFile getUserPreferencesFile(IProject project) { IFile defaultFile = project.getFile(DEFAULT_PREFS_PATH); IFile oldFile = project.getFile(DEPRECATED_PREFS_PATH); if (defaultFile.isAccessible() || !oldFile.isAccessible()) { return defaultFile; } return oldFile; } public static boolean isProjectSettingsEnabled(IProject project) { // fast path: read from session, if available Boolean enabled; try { enabled = (Boolean) project.getSessionProperty(SESSION_PROPERTY_SETTINGS_ON); } catch (CoreException e) { enabled = null; } if (enabled != null) { return enabled.booleanValue(); } // legacy support: before 1.3.8, there was ONLY project preferences in // .fbprefs // so check if the file is there... IFile file = getUserPreferencesFile(project); boolean projectPropsEnabled = file.isAccessible(); if (projectPropsEnabled) { ScopedPreferenceStore store = new ScopedPreferenceStore(new ProjectScope(project), PLUGIN_ID); // so if the file is there, we can check if after 1.3.8 the flag is // set // to use workspace properties instead projectPropsEnabled = !store.contains(FindBugsConstants.PROJECT_PROPS_DISABLED) || !store.getBoolean(FindBugsConstants.PROJECT_PROPS_DISABLED); } // remember in the session to speedup access, don't touch the store setProjectSettingsEnabled(project, null, projectPropsEnabled); return projectPropsEnabled; } public static void setProjectSettingsEnabled(IProject project, @CheckForNull IPreferenceStore store, boolean enabled) { try { project.setSessionProperty(SESSION_PROPERTY_SETTINGS_ON, Boolean.valueOf(enabled)); } catch (CoreException e) { FindbugsPlugin.getDefault().logException(e, "Error setting FindBugs session property for project"); } if (store != null) { store.setValue(FindBugsConstants.PROJECT_PROPS_DISABLED, !enabled); } } /** * Get the FindBugs core preferences for given project. This method can * return workspace preferences if project preferences are not created yet * or they are disabled. * * @param project * the project (if null, workspace settings are used) * @param forceRead * true to enforce reading properties from disk * * @return the preferences for the project or prefs from workspace */ public static UserPreferences getCorePreferences(@CheckForNull IProject project, boolean forceRead) { if (project == null || !isProjectSettingsEnabled(project)) { // read workspace (user) settings from instance area return getWorkspacePreferences(); } // use project settings return getProjectPreferences(project, forceRead); } /** * Get the Eclipse plugin preferences for given project. This method can * return workspace preferences if project preferences are not created yet * or they are disabled. * * @param project * the project (if null, workspace settings are used) * * @return the preferences for the project or prefs from workspace */ public static IPreferenceStore getPluginPreferences(@CheckForNull IProject project) { if (project == null || !isProjectSettingsEnabled(project)) { // read workspace (user) settings from instance area return new ScopedPreferenceStore(new InstanceScope(), FindbugsPlugin.PLUGIN_ID); } // use project settings return new ScopedPreferenceStore(new ProjectScope(project), FindbugsPlugin.PLUGIN_ID); } /** * Get project own preferences set. * * @param project * must be non null, exist and be opened * @param forceRead * @return current project preferences, independently if project preferences * are enabled or disabled for given project. */ public static UserPreferences getProjectPreferences(IProject project, boolean forceRead) { try { UserPreferences prefs = (UserPreferences) project.getSessionProperty(SESSION_PROPERTY_USERPREFS); if (prefs == null || forceRead) { prefs = readUserPreferences(project); if (prefs == null) { prefs = getWorkspacePreferences().clone(); } project.setSessionProperty(SESSION_PROPERTY_USERPREFS, prefs); } return prefs; } catch (CoreException e) { FindbugsPlugin.getDefault().logException(e, "Error getting FindBugs preferences for project"); return getWorkspacePreferences().clone(); } } private static UserPreferences getWorkspacePreferences() { // create initially default settings UserPreferences userPrefs = FindBugsPreferenceInitializer.createDefaultUserPreferences(); File prefsFile = WORKSPACE_PREFS_PATH.toFile(); if (!prefsFile.isFile()) { return userPrefs; } // load custom settings over defaults FileInputStream in; try { in = new FileInputStream(prefsFile); userPrefs.read(in); } catch (IOException e) { FindbugsPlugin.getDefault().logException(e, "Error reading custom FindBugs preferences for workspace"); } return userPrefs; } /** * Get the UserPreferences for given project. * * @param project * the project * @return the UserPreferences for the project */ public static UserPreferences getUserPreferences(IProject project) { return getCorePreferences(project, false); } /** * Save current UserPreferences for given project or workspace. * * @param project * the project or null for workspace * @throws CoreException */ public static void saveUserPreferences(IProject project, final UserPreferences userPrefs) throws CoreException { FileOutput userPrefsOutput = new FileOutput() { public void writeFile(OutputStream os) throws IOException { userPrefs.write(os); } public String getTaskDescription() { return "writing user preferences"; } }; if (project != null) { // Make the new user preferences current for the project project.setSessionProperty(SESSION_PROPERTY_USERPREFS, userPrefs); IFile userPrefsFile = getUserPreferencesFile(project); ensureReadWrite(userPrefsFile); IO.writeFile(userPrefsFile, userPrefsOutput, null); if (project.getFile(DEPRECATED_PREFS_PATH).equals(userPrefsFile)) { String message = "Found old style FindBugs preferences for project '" + project.getName() + "'. This preferences are not at the default location: '" + DEFAULT_PREFS_PATH + "'." + " Please move '" + DEPRECATED_PREFS_PATH + "' to '" + DEFAULT_PREFS_PATH + "'."; getDefault().logWarning(message); } } else { // write the workspace preferences to the eclipse preference store ByteArrayOutputStream bos = new ByteArrayOutputStream(10000); try { userPrefs.write(bos); } catch (IOException e) { getDefault().logException(e, "Failed to write user preferences"); return; } Properties props = new Properties(); try { props.load(new ByteArrayInputStream(bos.toByteArray())); } catch (IOException e) { getDefault().logException(e, "Failed to save user preferences"); return; } IPreferenceStore store = getDefault().getPreferenceStore(); // Reset any existing custom plugins entries int start = 0; // 99 is paranoia. while(start < 99){ String name = UserPreferences.KEY_PLUGIN + start; if(store.contains(name)){ store.setToDefault(name); } else { break; } start ++; } for (Entry<Object, Object> entry : props.entrySet()) { store.putValue((String) entry.getKey(), (String) entry.getValue()); } if(store instanceof IPersistentPreferenceStore){ IPersistentPreferenceStore store2 = (IPersistentPreferenceStore) store; try { store2.save(); } catch (IOException e) { getDefault().logException(e, "Failed to save user preferences"); } } } } /** * Ensure that a file is writable. If not currently writable, check it as so * that we can edit it. * * @param file * - file that should be made writable * @throws CoreException */ private static void ensureReadWrite(IFile file) throws CoreException { /* * fix for bug 1683264: we should checkout file before writing to it */ if (file.isReadOnly()) { IStatus checkOutStatus = ResourcesPlugin.getWorkspace().validateEdit(new IFile[] { file }, null); if (!checkOutStatus.isOK()) { throw new CoreException(checkOutStatus); } } } /** * Read UserPreferences for project from the file in the project directory. * Returns null if the preferences have not been saved to a file, or if * there is an error reading the preferences file. * * @param project * the project to get the UserPreferences for * @return the UserPreferences, or null if the UserPreferences file could * not be read * @throws CoreException */ private static UserPreferences readUserPreferences(IProject project) throws CoreException { IFile userPrefsFile = getUserPreferencesFile(project); if (!userPrefsFile.exists()) { return null; } try { // force is preventing us for out-of-sync exception if file was // changed externally InputStream in = userPrefsFile.getContents(true); UserPreferences userPrefs = FindBugsPreferenceInitializer.createDefaultUserPreferences(); userPrefs.read(in); return userPrefs; } catch (IOException e) { FindbugsPlugin.getDefault().logException(e, "Could not read user preferences for project"); return null; } } public BugResolutionAssociations getBugResolutions() { if (!bugResolutionsLoaded) { bugResolutionsLoaded = true; try { bugResolutions = loadBugResolutions(); } catch (Exception e) { FindbugsPlugin.getDefault().logException(e, "Could not read load bug resolutions"); } } return bugResolutions; } private static BugResolutionAssociations loadBugResolutions() { BugResolutionLoader loader = new BugResolutionLoader(); return loader.loadBugResolutions(); } public static void showMarker(IMarker marker, String viewId, IWorkbenchPart source) { IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); IViewPart view = page.findView(viewId); if (!page.isPartVisible(view)) { try { view = page.showView(viewId); } catch (PartInitException e) { FindbugsPlugin.getDefault().logException(e, "Could not open view: " + viewId); return; } } if (view instanceof IMarkerSelectionHandler) { IMarkerSelectionHandler handler = (IMarkerSelectionHandler) view; handler.markerSelected(source, marker); } else if (DETAILS_VIEW_ID.equals(viewId) && view instanceof ISelectionListener) { ISelectionListener listener = (ISelectionListener) view; listener.selectionChanged(source, new StructuredSelection(marker)); } } /** * Call this method to retrieve the (cache) ImageDescriptor for the given * id. * * @param id * the id of the image descriptor or relative icon path if icon * is inside of default icons folder * @return the ImageDescriptor instance. */ public ImageDescriptor getImageDescriptor(String id) { ImageDescriptor imageDescriptor = imageDescriptors.get(id); if (imageDescriptor == null) { String pluginId = getDefault().getBundle().getSymbolicName(); imageDescriptor = AbstractUIPlugin.imageDescriptorFromPlugin(pluginId, ICON_PATH + id); imageDescriptors.put(id, imageDescriptor); } return imageDescriptor; } public static Set<BugPattern> getKnownPatterns() { Set<BugPattern> patterns = new TreeSet<BugPattern>(); Iterator<BugPattern> patternIterator = DetectorFactoryCollection.instance().bugPatternIterator(); while (patternIterator.hasNext()) { patterns.add(patternIterator.next()); } return patterns; } public static Set<BugCode> getKnownPatternTypes() { Set<BugCode> patterns = new TreeSet<BugCode>(DetectorFactoryCollection.instance().getBugCodes()); return patterns; } public static Set<String> getFilteredIds() { final IPreferenceStore store = FindbugsPlugin.getDefault().getPreferenceStore(); String lastUsedFilter = store.getString(FindBugsConstants.LAST_USED_EXPORT_FILTER); return FindBugsConstants.decodeIds(lastUsedFilter); } public static Set<BugPattern> getFilteredPatterns() { Iterator<BugPattern> patternIterator = DetectorFactoryCollection.instance().bugPatternIterator(); Set<BugPattern> set = new HashSet<BugPattern>(); Set<String> patternTypes = getFilteredIds(); while (patternIterator.hasNext()) { BugPattern next = patternIterator.next(); String patternId = next.getType(); if (!patternTypes.contains(patternId)) { continue; } set.add(next); } return set; } public static Set<BugCode> getFilteredPatternTypes() { Set<BugCode> set = new HashSet<BugCode>(); Set<String> patternTypes = getFilteredIds(); for(BugCode next : DetectorFactoryCollection.instance().getBugCodes()) { String type = next.getAbbrev(); if (!patternTypes.contains(type)) { continue; } set.add(next); } return set; } public static void clearBugCollection(IProject project) throws CoreException { createDefaultEmptyBugCollection(project); markBugCollectionDirty(project, true); saveCurrentBugCollection(project, null); } public static void log(String msg) { log(msg, null); } public static void log(String msg, Exception e) { plugin.getLog().log(new Status(IStatus.INFO, FindbugsPlugin.PLUGIN_ID, msg, e)); } }