/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2003-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 edu.umd.cs.findbugs;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.swing.JOptionPane;
import edu.umd.cs.findbugs.cloud.CloudPlugin;
import edu.umd.cs.findbugs.util.ClassPathUtil;
/**
* The DetectorFactoryCollection stores all of the DetectorFactory objects used
* to create the Detectors which implement the various analyses. It is a
* singleton class.
*
* @author David Hovemeyer
* @see DetectorFactory
*/
public class DetectorFactoryCollection {
private static final Logger LOGGER = Logger.getLogger(DetectorFactoryCollection.class.getName());
private static final boolean DEBUG_JAWS = SystemProperties.getBoolean("findbugs.jaws.debug");
private final HashMap<String, Plugin> pluginByIdMap = new LinkedHashMap<String, Plugin>();
private Plugin corePlugin;
private final ArrayList<DetectorFactory> factoryList = new ArrayList<DetectorFactory>();
private final HashMap<String, DetectorFactory> factoriesByName = new HashMap<String, DetectorFactory>();
private final HashMap<String, DetectorFactory> factoriesByDetectorClassName = new HashMap<String, DetectorFactory>();
private static DetectorFactoryCollection theInstance;
private static final Object lock = new Object();
static final boolean DEBUG = Boolean.getBoolean("dfc.debug");
Map<String, CloudPlugin> registeredClouds = new LinkedHashMap<String, CloudPlugin>();
protected final HashMap<String, BugCategory> categoryDescriptionMap = new HashMap<String, BugCategory>();
protected final HashMap<String, BugPattern> bugPatternMap = new HashMap<String, BugPattern>();
protected final HashMap<String, BugCode> bugCodeMap = new HashMap<String, BugCode>();
protected DetectorFactoryCollection() {
loadCorePlugin();
for(Plugin plugin : Plugin.getAllPlugins()) {
if (plugin.isGloballyEnabled() && !plugin.isCorePlugin())
loadPlugin(plugin);
}
}
protected DetectorFactoryCollection(Plugin onlyPlugin) {
loadPlugin(onlyPlugin);
}
protected DetectorFactoryCollection(Collection<Plugin> enabled) {
if (FindBugs.DEBUG)
System.out.println("\nCreating custom DFC {");
loadCorePlugin();
for(Plugin plugin : enabled) {
loadPlugin(plugin);
}
if (FindBugs.DEBUG)
System.out.println("}\n");
}
/**
* Reset the factory singleton.
* <p>
* <b>Implementation note:</b> This method is public for tests only!
*
* @param instance
* can be null
*/
public static void resetInstance(DetectorFactoryCollection instance) {
synchronized (lock) {
if (instance == null)
throw new IllegalArgumentException();
theInstance = instance;
}
}
/**
* Get the single instance of DetectorFactoryCollection, which knows each and every
* loaded plugin, independently of it's enablement
*/
public static DetectorFactoryCollection instance() {
synchronized (lock) {
if (theInstance == null) {
theInstance = new DetectorFactoryCollection();
}
return theInstance;
}
}
/**
* Return an Iterator over all available Plugin objects.
*/
public Iterator<Plugin> pluginIterator() {
return pluginByIdMap.values().iterator();
}
/**
* Return an Collection of all available Plugin objects.
*/
public Collection<Plugin> plugins() {
return pluginByIdMap.values();
}
@Nonnull
public Plugin getCorePlugin() {
if (corePlugin == null)
throw new IllegalStateException("No core plugin");
return corePlugin;
}
/**
* Get a Plugin by its unique id.
*
* @param pluginId
* the unique id
* @return the Plugin with that id, or null if no such Plugin is found
*/
public Plugin getPluginById(String pluginId) {
return pluginByIdMap.get(pluginId);
}
/**
* Return an Iterator over the DetectorFactory objects for all registered
* Detectors.
*/
public Iterator<DetectorFactory> factoryIterator() {
return factoryList.iterator();
}
/**
* Return an Iterable over the DetectorFactory objects for all registered
* Detectors.
*/
public Iterable<DetectorFactory> getFactories() {
return factoryList;
}
/**
* Look up a DetectorFactory by its short name.
*
* @param name
* the short name
* @return the DetectorFactory, or null if there is no factory with that
* short name
*/
public DetectorFactory getFactory(String name) {
return factoriesByName.get(name);
}
/**
* Look up a DetectorFactory by its class name.
*
* @param className
* the class name
* @return the DetectoryFactory, or null if there is no factory with that
* class name
*/
public DetectorFactory getFactoryByClassName(String className) {
return factoriesByDetectorClassName.get(className);
}
/**
* Register a DetectorFactory.
*/
void registerDetector(DetectorFactory factory) {
if (FindBugs.DEBUG)
System.out.println("Registering detector: " + factory.getFullName());
String detectorName = factory.getShortName();
factoryList.add(factory);
factoriesByName.put(detectorName, factory);
factoriesByDetectorClassName.put(factory.getFullName(), factory);
}
void unRegisterDetector(DetectorFactory factory) {
if (FindBugs.DEBUG)
System.out.println("Unregistering detector: " + factory.getFullName());
String detectorName = factory.getShortName();
factoryList.remove(factory);
factoriesByName.remove(detectorName);
factoriesByDetectorClassName.remove(factory.getFullName());
}
private static final Pattern[] findbugsJarNames = { Pattern.compile("findbugs\\.jar$"), };
/**
* See if the location of ${findbugs.home} can be inferred from the location
* of findbugs.jar in the classpath.
*
* @return inferred ${findbugs.home}, or null if we can't figure it out
*/
private static String inferFindBugsHome() {
for (Pattern jarNamePattern : findbugsJarNames) {
String findbugsJarCodeBase = ClassPathUtil.findCodeBaseInClassPath(jarNamePattern,
SystemProperties.getProperty("java.class.path"));
if (findbugsJarCodeBase != null) {
File findbugsJar = new File(findbugsJarCodeBase);
File libDir = findbugsJar.getParentFile();
if (libDir.getName().equals("lib")) {
String fbHome = libDir.getParent();
FindBugs.setHome(fbHome);
return fbHome;
}
}
}
String classFilePath = FindBugs.class.getName().replaceAll("\\.", "/") + ".class";
URL resource = FindBugs.class.getClassLoader().getResource(classFilePath);
if (resource != null && resource.getProtocol().equals("file")) {
try {
String classfile = URLDecoder.decode(resource.getPath(), Charset.defaultCharset().name());
Matcher m = Pattern.compile("(.*)/.*?/edu/umd.*").matcher(classfile);
if (m.matches()) {
String home = m.group(1);
if (new File(home + "/etc/findbugs.xml").exists()) {
FindBugs.setHome(home);
return home;
}
}
} catch (UnsupportedEncodingException e) {
}
}
return null;
}
public static String getFindBugsHome() {
String homeDir = FindBugs.getHome();
if (homeDir == null) {
// Attempt to infer findbugs.home from the observed
// location of findbugs.jar.
homeDir = inferFindBugsHome();
}
return homeDir;
}
@CheckForNull
public static URL getCoreResource(String name) {
return PluginLoader.getCoreResource(name);
}
/**
* Load the core plugin.
* @throws PluginException
*/
private void loadCorePlugin() {
Plugin plugin = PluginLoader.getCorePluginLoader().getPlugin();
loadPlugin(plugin);
corePlugin = plugin;
}
/**
* @param message
*/
public static void jawsDebugMessage(String message) {
if (DEBUG_JAWS)
JOptionPane.showMessageDialog(null, message);
else if (FindBugs.DEBUG)
System.err.println(message);
}
public static void jawsErrorMessage(String message) {
if (DEBUG_JAWS)
JOptionPane.showMessageDialog(null, message);
else
System.err.println(message);
}
void loadPlugin(Plugin plugin) {
if (FindBugs.DEBUG) {
System.out.println("Loading " + plugin.getPluginId());
}
pluginByIdMap.put(plugin.getPluginId(), plugin);
// Register all of the detectors that this plugin contains
for (DetectorFactory factory : plugin.getDetectorFactories()) {
registerDetector(factory);
}
for (BugCategory bugCategory : plugin.getBugCategories()) {
registerBugCategory(bugCategory);
}
// Register the BugPatterns
for (BugPattern bugPattern : plugin.getBugPatterns()) {
registerBugPattern(bugPattern);
}
// Register the BugCodes
for (BugCode bugCode : plugin.getBugCodes()) {
registerBugCode(bugCode);
}
for (CloudPlugin cloud : plugin.getCloudPlugins()) {
registerCloud(cloud);
}
}
void unLoadPlugin(Plugin plugin) {
pluginByIdMap.remove(plugin.getPluginId());
for (DetectorFactory factory : plugin.getDetectorFactories()) {
unRegisterDetector(factory);
}
for (BugCategory bugCategory : plugin.getBugCategories()) {
unRegisterBugCategory(bugCategory);
}
for (BugPattern bugPattern : plugin.getBugPatterns()) {
unRegisterBugPattern(bugPattern);
}
for (BugCode bugCode : plugin.getBugCodes()) {
unRegisterBugCode(bugCode);
}
for (CloudPlugin cloud : plugin.getCloudPlugins()) {
unRegisterCloud(cloud);
}
}
public Map<String, CloudPlugin> getRegisteredClouds() {
return Collections.unmodifiableMap(registeredClouds);
}
/**
* @param cloudPlugin
*/
void registerCloud(CloudPlugin cloudPlugin) {
LOGGER.log(Level.FINE, "Registering " + cloudPlugin.getId());
registeredClouds.put(cloudPlugin.getId(), cloudPlugin);
}
void unRegisterCloud(CloudPlugin cloudPlugin) {
LOGGER.log(Level.FINE, "Unregistering " + cloudPlugin.getId());
registeredClouds.remove(cloudPlugin.getId());
}
/**
* Set the metadata for a bug category. If the category's metadata has
* already been set, this does nothing.
* @param bc
* the BugCategory object holding the metadata for the category
*
* @return false if the category's metadata has already been set, true
* otherwise
*/
public boolean registerBugCategory(BugCategory bc) {
String category = bc.getCategory();
if (categoryDescriptionMap.get(category) != null)
return false;
categoryDescriptionMap.put(category, bc);
return true;
}
protected boolean unRegisterBugCategory(BugCategory bc) {
String category = bc.getCategory();
categoryDescriptionMap.remove(category);
return true;
}
/**
* Register a BugPattern.
*
* @param bugPattern
* the BugPattern
*/
public void registerBugPattern(BugPattern bugPattern) {
bugPatternMap.put(bugPattern.getType(), bugPattern);
}
protected void unRegisterBugPattern(BugPattern bugPattern) {
bugPatternMap.remove(bugPattern.getType());
}
/**
* Get an Iterator over all registered bug patterns.
*/
public Iterator<BugPattern> bugPatternIterator() {
return bugPatternMap.values().iterator();
}
/**
* Get an Iterator over all registered bug patterns.
*/
public Collection<BugPattern> getBugPatterns() {
return bugPatternMap.values();
}
/**
* Look up bug pattern.
*
* @param bugType
* the bug type for the bug pattern
* @return the BugPattern, or null if it can't be found
*/
public @CheckForNull BugPattern lookupBugPattern(String bugType) {
return bugPatternMap.get(bugType);
}
/**
* Get an Iterator over all registered bug codes.
*/
public Iterator<BugCode> bugCodeIterator() {
return bugCodeMap.values().iterator();
}
/**
* Register a BugCode.
*
* @param bugCode
* the BugCode
*/
public void registerBugCode(BugCode bugCode) {
bugCodeMap.put(bugCode.getAbbrev(), bugCode);
}
protected void unRegisterBugCode(BugCode bugCode) {
bugCodeMap.remove(bugCode.getAbbrev());
}
/**
* Get a description for given "bug type". FIXME: this is referred to
* elsewhere as the "bug code" or "bug abbrev". Should make the terminology
* consistent everywhere. In this case, the bug type refers to the short
* prefix code prepended to the long and short bug messages.
*
* @param shortBugType
* the short bug type code
* @return the description of that short bug type code means
*/
public BugCode getBugCode(String shortBugType) {
BugCode bugCode = lookupBugCode(shortBugType);
if (bugCode == null)
throw new IllegalArgumentException("Error: missing bug code for key" + shortBugType);
return bugCode;
}
/**
* @param shortBugType the short bug type code
* @return the description of that short bug type code means
*/
public BugCode lookupBugCode(String shortBugType) {
return bugCodeMap.get(shortBugType);
}
/**
* Get the BugCategory object for a category key. Returns null if no
* BugCategory object can be found.
*
* @param category
* the category key
* @return the BugCategory object (may be null)
*/
public BugCategory getBugCategory(String category) {
return categoryDescriptionMap.get(category);
}
/**
* Get a Collection containing all known bug category keys. E.g.,
* "CORRECTNESS", "MT_CORRECTNESS", "PERFORMANCE", etc.
*
* @return Collection of bug category keys.
*/
public Collection<String> getBugCategories() {
return categoryDescriptionMap.keySet(); // backed by the Map
}
public Collection<BugCategory> getBugCategoryObjects() {
return categoryDescriptionMap.values(); // backed by the Map
}
}