/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2004, 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 edu.umd.cs.findbugs;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.CheckForNull;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.classfile.IAnalysisEngineRegistrar;
import edu.umd.cs.findbugs.cloud.CloudPlugin;
import edu.umd.cs.findbugs.plan.DetectorOrderingConstraint;
import edu.umd.cs.findbugs.util.DualKeyHashMap;
/**
* A FindBugs plugin. A plugin contains executable Detector classes, as well as
* meta information describing those detectors (such as human-readable detector
* and bug descriptions).
*
* @see PluginLoader
* @author David Hovemeyer
*/
public class Plugin {
static final Map<String, String> globalOptions = new HashMap<String,String>();
static final Map<String, Plugin> globalOptionsSetter = new HashMap<String,Plugin>();
public static Map<String, String> getGlobalOptions() {
return Collections.unmodifiableMap(globalOptions);
}
private static final String USE_FINDBUGS_VERSION = "USE_FINDBUGS_VERSION";
private final String pluginId;
private final String version;
private String provider;
private URI website;
private @CheckForNull URI usageTracker;
private String shortDescription;
private String detailedDescription;
private final ArrayList<DetectorFactory> detectorFactoryList;
private final Map<String, FindBugsMain> mainPlugins;
private final LinkedHashSet<BugPattern> bugPatterns;
private final LinkedHashSet<BugCode> bugCodeList;
private final LinkedHashSet<BugCategory> bugCategoryList;
private final LinkedHashSet<CloudPlugin> cloudList;
private final DualKeyHashMap<Class, String, ComponentPlugin> componentPlugins;
private BugRanker bugRanker;
// Ordering constraints
private final ArrayList<DetectorOrderingConstraint> interPassConstraintList;
private final ArrayList<DetectorOrderingConstraint> intraPassConstraintList;
// Optional: engine registrar class
private Class<? extends IAnalysisEngineRegistrar> engineRegistrarClass;
// PluginLoader that loaded this plugin
private final PluginLoader pluginLoader;
private final boolean enabledByDefault;
static Map<URI, Plugin> allPlugins = new LinkedHashMap<URI, Plugin>();
enum EnabledState { PLUGIN_DEFAULT, ENABLED, DISABLED};
private EnabledState enabled;
/**
* Constructor. Creates an empty plugin object.
*
* @param pluginId
* the plugin's unique identifier
* @param version TODO
* @param enabled TODO
*/
public Plugin(String pluginId, String version, PluginLoader pluginLoader, boolean enabled) {
this.pluginId = pluginId;
if (version == null) {
version = "";
} else if (version.equals(USE_FINDBUGS_VERSION)) {
version = Version.COMPUTED_RELEASE;
}
cloudList = new LinkedHashSet<CloudPlugin>();
componentPlugins = new DualKeyHashMap<Class, String, ComponentPlugin> ();
this.version = version;
this.detectorFactoryList = new ArrayList<DetectorFactory>();
this.bugPatterns = new LinkedHashSet<BugPattern>();
this.bugCodeList = new LinkedHashSet<BugCode>();
this.bugCategoryList = new LinkedHashSet<BugCategory>();
this.interPassConstraintList = new ArrayList<DetectorOrderingConstraint>();
this.intraPassConstraintList = new ArrayList<DetectorOrderingConstraint>();
this.mainPlugins = new HashMap<String, FindBugsMain>();
this.pluginLoader = pluginLoader;
this.enabledByDefault = enabled;
this.enabled = EnabledState.PLUGIN_DEFAULT;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + ":" + pluginId;
}
/**
* Return whether or not the Plugin is enabled.
*
* @return true if the Plugin is enabled, false if not
*/
public boolean isEnabledByDefault() {
return enabledByDefault;
}
/**
* Set plugin provider.
*
* @param provider
* the plugin provider
*/
public void setProvider(String provider) {
this.provider = provider;
}
/**
* Get the plugin provider.
*
* @return the provider, or null if the provider was not specified
*/
public @CheckForNull String getProvider() {
return provider;
}
public void setUsageTracker(String tracker) throws URISyntaxException {
this.usageTracker = new URI(tracker);
}
public @CheckForNull URI getUsageTracker() {
return usageTracker;
}
/**
* Set plugin website.
*
* @param website
* the plugin website
* @throws URISyntaxException
*/
public void setWebsite(String website) throws URISyntaxException {
this.website = new URI(website);
}
/**
* Get the plugin website.
*
* @return the website, or null if the was not specified
*/
public @CheckForNull String getWebsite() {
if (website == null)
return null;
return website.toASCIIString();
}
public @CheckForNull URI getWebsiteURI() {
return website;
}
/** Get the version of the plugin */
public String getVersion() {
return version;
}
/**
* Set plugin short (one-line) text description.
*
* @param shortDescription
* the plugin short text description
*/
public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}
/**
* Get the plugin short (one-line) description.
*
* @return the short description, or null if the short description was not
* specified
*/
public String getShortDescription() {
return shortDescription;
}
public String getDetailedDescription() {
return detailedDescription;
}
public void setDetailedDescription(String detailedDescription) {
this.detailedDescription = detailedDescription;
}
/**
* Add a DetectorFactory for a Detector implemented by the Plugin.
*
* @param factory
* the DetectorFactory
*/
public void addDetectorFactory(DetectorFactory factory) {
detectorFactoryList.add(factory);
}
public void addCloudPlugin(CloudPlugin cloudPlugin) {
cloudList.add(cloudPlugin);
}
/**
* Add a BugPattern reported by the Plugin.
*
* @param bugPattern
*/
public void addBugPattern(BugPattern bugPattern) {
bugPatterns.add(bugPattern);
}
/**
* Add a BugCode reported by the Plugin.
*
* @param bugCode
*/
public void addBugCode(BugCode bugCode) {
bugCodeList.add(bugCode);
}
/**
* Add a BugCategory reported by the Plugin.
*
* @param bugCode
*/
public void addBugCategory(BugCategory bugCategory) {
bugCategoryList.add(bugCategory);
}
/**
* Add an inter-pass Detector ordering constraint.
*
* @param constraint
* the inter-pass Detector ordering constraint
*/
public void addInterPassOrderingConstraint(DetectorOrderingConstraint constraint) {
interPassConstraintList.add(constraint);
}
/**
* Add an intra-pass Detector ordering constraint.
*
* @param constraint
* the intra-pass Detector ordering constraint
*/
public void addIntraPassOrderingConstraint(DetectorOrderingConstraint constraint) {
intraPassConstraintList.add(constraint);
}
/**
* Look up a DetectorFactory by short name.
*
* @param shortName
* the short name
* @return the DetectorFactory
*/
public DetectorFactory getFactoryByShortName(final String shortName) {
return findFirstMatchingFactory(new FactoryChooser() {
public boolean choose(DetectorFactory factory) {
return factory.getShortName().equals(shortName);
}
});
}
/**
* Look up a DetectorFactory by full name.
*
* @param fullName
* the full name
* @return the DetectorFactory
*/
public DetectorFactory getFactoryByFullName(final String fullName) {
return findFirstMatchingFactory(new FactoryChooser() {
public boolean choose(DetectorFactory factory) {
return factory.getFullName().equals(fullName);
}
});
}
/**
* Get Iterator over DetectorFactory objects in the Plugin.
*
* @return Iterator over DetectorFactory objects
*/
public Collection<DetectorFactory> getDetectorFactories() {
return detectorFactoryList;
}
/**
* Get the set of BugPatterns
*
*/
public Set<BugPattern> getBugPatterns() {
return bugPatterns;
}
/**
* Get Iterator over BugCode objects in the Plugin.
*
* @return Iterator over BugCode objects
*/
public Set<BugCode> getBugCodes() {
return bugCodeList;
}
/**
* Get Iterator over BugCategories objects in the Plugin.
*
* @return Iterator over BugCategory objects
*/
public Set<BugCategory> getBugCategories() {
return bugCategoryList;
}
/**
* @param id may be null
* @return return bug category with given id, may return null if the bug category is unknown
*/
@CheckForNull
public BugCategory getBugCategory(String id) {
for (BugCategory bc : bugCategoryList) {
if(bc.getCategory().equals(id)){
return bc;
}
}
return null;
}
public Set<CloudPlugin> getCloudPlugins() {
return cloudList;
}
/**
* Return an Iterator over the inter-pass Detector ordering constraints.
*/
public Iterator<DetectorOrderingConstraint> interPassConstraintIterator() {
return interPassConstraintList.iterator();
}
/**
* Return an Iterator over the intra-pass Detector ordering constraints.
*/
public Iterator<DetectorOrderingConstraint> intraPassConstraintIterator() {
return intraPassConstraintList.iterator();
}
/**
* @return Returns the pluginId.
*/
public String getPluginId() {
return pluginId;
}
/**
* @return Returns the short pluginId.
*/
public String getShortPluginId() {
int i = pluginId.lastIndexOf('.');
return pluginId.substring(i+1);
}
/**
* Set the analysis engine registrar class that, when instantiated, can be
* used to register the plugin's analysis engines with the analysis cache.
*
* @param engineRegistrarClass
* The engine registrar class to set.
*/
public void setEngineRegistrarClass(Class<? extends IAnalysisEngineRegistrar> engineRegistrarClass) {
this.engineRegistrarClass = engineRegistrarClass;
}
/**
* Get the analysis engine registrar class that, when instantiated, can be
* used to register the plugin's analysis engines with the analysis cache.
*
* @return Returns the engine registrar class.
*/
public Class<? extends IAnalysisEngineRegistrar> getEngineRegistrarClass() {
return engineRegistrarClass;
}
/**
* @return Returns the pluginLoader.
*/
public PluginLoader getPluginLoader() {
return pluginLoader;
}
private interface FactoryChooser {
public boolean choose(DetectorFactory factory);
}
private @CheckForNull
DetectorFactory findFirstMatchingFactory(FactoryChooser chooser) {
for (DetectorFactory factory : getDetectorFactories()) {
if (chooser.choose(factory))
return factory;
}
return null;
}
/**
* @param ranker
*/
public void setBugRanker(BugRanker ranker) {
this.bugRanker = ranker;
}
public BugRanker getBugRanker() {
return bugRanker;
}
<T> void addFindBugsMain(Class<?> mainClass, String cmd, String kind, boolean analysis) throws SecurityException, NoSuchMethodException {
FindBugsMain main = new FindBugsMain(mainClass, cmd, kind, analysis);
mainPlugins.put(cmd, main);
}
public @CheckForNull FindBugsMain getFindBugsMain(String cmd) {
return mainPlugins.get(cmd);
}
<T> void addComponentPlugin(Class<T> componentClass, ComponentPlugin<T> plugin) {
if (!componentClass.isAssignableFrom(plugin.getComponentClass()))
throw new IllegalArgumentException();
componentPlugins.put(componentClass, plugin.getId(), plugin);
}
public <T> Iterable<ComponentPlugin<T>> getComponentPlugins(Class<T> componentClass) {
Collection values = componentPlugins.get(componentClass).values();
return values;
}
public <T> ComponentPlugin<T> getComponentPlugin(Class<T> componentClass, String name) {
return componentPlugins.get(componentClass, name);
}
public static synchronized @CheckForNull Plugin getByPluginId(String name) {
for(Plugin plugin : allPlugins.values()) {
if (name.equals(plugin.getPluginId()) || name.equals(plugin.getShortPluginId()))
return plugin;
}
return null;
}
/**
* @return a copy of the internal plugins collection
*/
public static synchronized Collection<Plugin> getAllPlugins() {
return new ArrayList<Plugin>(allPlugins.values());
}
public static synchronized Set<URI> getAllPluginsURIs() {
Collection<Plugin> plugins = getAllPlugins();
Set<URI> uris = new HashSet<URI>();
for (Plugin plugin : plugins) {
try {
URI uri = plugin.getPluginLoader().getURL().toURI();
if(uri != null) {
uris.add(uri);
}
} catch (URISyntaxException e) {
AnalysisContext.logError("Unable to get URI", e);
}
}
return uris;
}
/**
* @return may return null
*/
@CheckForNull
static synchronized Plugin getPlugin(URI uri) {
return allPlugins.get(uri);
}
/**
* @return may return null
*/
@CheckForNull
static synchronized Plugin putPlugin(URI uri, Plugin plugin) {
return allPlugins.put(uri, plugin);
}
public boolean isCorePlugin() {
return pluginLoader.isCorePlugin();
}
public boolean isGloballyEnabled() {
if (isCorePlugin())
return true;
switch (enabled) {
case ENABLED:
return true;
case DISABLED:
return false;
case PLUGIN_DEFAULT:
return isEnabledByDefault();
default:
throw new IllegalStateException("Unknown state : " + enabled);
}
}
/**
* @return
*/
public void setGloballyEnabled(boolean enabled) {
if (isCorePlugin()) {
if (!enabled)
throw new IllegalArgumentException("Can't disable core plugin");
return;
}
EnabledState oldState = this.enabled;
if (enabled) {
if (isEnabledByDefault())
this.enabled = EnabledState.PLUGIN_DEFAULT;
else
this.enabled = EnabledState.ENABLED;
} else {
if (isEnabledByDefault())
this.enabled = EnabledState.DISABLED;
else
this.enabled = EnabledState.PLUGIN_DEFAULT;
}
if(oldState != this.enabled) {
// TODO update detector factory collection?
}
}
public boolean isInitialPlugin() {
return getPluginLoader().initialPlugin;
}
public URL getResource(String name) {
return getPluginLoader().getResource(name);
}
public ClassLoader getClassLoader() {
return getPluginLoader().getClassLoader();
}
/**
* Loads the given plugin and enables it for the given project.
*/
public static Plugin loadCustomPlugin(File f, @CheckForNull Project project)
throws PluginException {
URL urlString;
try {
urlString = f.toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
return loadCustomPlugin(urlString, project);
}
/**
* Loads the given plugin and enables it for the given project.
*/
public static Plugin loadCustomPlugin(URL urlString, @CheckForNull Project project) throws PluginException {
Plugin plugin = addCustomPlugin(urlString);
if (project != null) {
project.setPluginStatus(plugin.getPluginId(), true);
}
return plugin;
}
public static Plugin addCustomPlugin(URL u) throws PluginException {
return addCustomPlugin(u, PluginLoader.class.getClassLoader());
}
public static Plugin addCustomPlugin(URI u) throws PluginException {
return addCustomPlugin(u, PluginLoader.class.getClassLoader());
}
public static Plugin addCustomPlugin(URL u, ClassLoader parent) throws PluginException {
PluginLoader pluginLoader = PluginLoader.getPluginLoader(u, parent, false, true);
Plugin plugin = pluginLoader.loadPlugin();
// register new clouds
DetectorFactoryCollection.instance().loadPlugin(plugin);
return plugin;
}
public static Plugin addCustomPlugin(URI u, ClassLoader parent) throws PluginException {
try {
PluginLoader pluginLoader = PluginLoader.getPluginLoader(u.toURL(), parent, false, true);
Plugin plugin = pluginLoader.loadPlugin();
// register new clouds
DetectorFactoryCollection.instance().loadPlugin(plugin);
return plugin;
} catch (MalformedURLException e) {
throw new PluginException("Unable to convert uri to url:" + u, e);
}
}
public static synchronized void removeCustomPlugin(Plugin plugin) {
Set<Entry<URI, Plugin>> entrySet = Plugin.allPlugins.entrySet();
for (Entry<URI, Plugin> entry : entrySet) {
if(entry.getValue() == plugin) {
Plugin.allPlugins.remove(entry.getKey());
PluginLoader.loadedPluginIds.remove(plugin.getPluginId());
break;
}
}
DetectorFactoryCollection.instance().unLoadPlugin(plugin);
}
}