/*
* 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.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.charsets.UTF8;
import edu.umd.cs.findbugs.classfile.IAnalysisEngineRegistrar;
import edu.umd.cs.findbugs.cloud.Cloud;
import edu.umd.cs.findbugs.cloud.CloudFactory;
import edu.umd.cs.findbugs.cloud.CloudPlugin;
import edu.umd.cs.findbugs.cloud.CloudPluginBuilder;
import edu.umd.cs.findbugs.cloud.username.NameLookup;
import edu.umd.cs.findbugs.io.IO;
import edu.umd.cs.findbugs.plan.ByInterfaceDetectorFactorySelector;
import edu.umd.cs.findbugs.plan.DetectorFactorySelector;
import edu.umd.cs.findbugs.plan.DetectorOrderingConstraint;
import edu.umd.cs.findbugs.plan.ReportingDetectorFactorySelector;
import edu.umd.cs.findbugs.plan.SingleDetectorFactorySelector;
import edu.umd.cs.findbugs.plugins.DuplicatePluginIdDescriptor;
import edu.umd.cs.findbugs.util.ClassName;
import edu.umd.cs.findbugs.util.JavaWebStart;
import edu.umd.cs.findbugs.util.Util;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
/**
* Loader for a FindBugs plugin. A plugin is a jar file containing two metadata
* files, "findbugs.xml" and "messages.xml". Those files specify
* <ul>
* <li>the bug pattern Detector classes,
* <li>the bug patterns detected (including all text for displaying detected
* instances of those patterns), and
* <li>the "bug codes" which group together related bug instances
* </ul>
*
* <p>
* The PluginLoader creates a Plugin object to store the Detector factories and
* metadata.
* </p>
*
* @author David Hovemeyer
* @see Plugin
* @see PluginException
*/
public class PluginLoader {
private static final boolean DEBUG = SystemProperties.getBoolean("findbugs.debug.PluginLoader");
// ClassLoader used to load classes and resources
private final ClassLoader classLoader;
private final ClassLoader classLoaderForResources;
// Keep a count of how many plugins we've seen without a
// "pluginid" attribute, so we can assign them all unique ids.
private static int nextUnknownId;
// The loaded Plugin
private Plugin plugin;
private final boolean corePlugin;
boolean initialPlugin;
private boolean optionalPlugin;
private final URL loadedFrom;
private final String jarName;
private final URI loadedFromUri;
static HashSet<String> loadedPluginIds = new HashSet<String>();
static {
if (DEBUG) {
System.out.println("Debugging plugin loading. FindBugs version "
+ Version.getReleaseWithDateIfDev());
}
loadInitialPlugins();
}
/**
* Constructor.
*
* @param url
* the URL of the plugin Jar file
* @throws PluginException
* if the plugin cannot be fully loaded
*/
@Deprecated
public PluginLoader(URL url) throws PluginException {
this(url, toUri(url), null, false, true);
}
/**
* Constructor.
*
* @param url
* the URL of the plugin Jar file
* @param parent
* the parent classloader
* @deprecated Use {@link #PluginLoader(URL,ClassLoader,boolean,boolean)} instead
*/
@Deprecated
public PluginLoader(URL url, ClassLoader parent) throws PluginException {
this(url, toUri(url), parent, false, true);
}
/**
* Constructor.
*
* @param url
* the URL of the plugin Jar file
* @param uri
* @param parent
* the parent classloader
* @param isInitial TODO
* @param optional TODO
*/
private PluginLoader(URL url, URI uri, ClassLoader parent, boolean isInitial, boolean optional) throws PluginException {
classLoader = new URLClassLoader(new URL[] { url }, parent);
classLoaderForResources = new URLClassLoader(new URL[] { url });
loadedFrom = url;
loadedFromUri = uri;
jarName = getJarName(url);
corePlugin = false;
initialPlugin = isInitial;
optionalPlugin = optional;
plugin = init();
Plugin.putPlugin(loadedFromUri, plugin);
}
/**
* Constructor. Loads a plugin using the caller's class loader. This
* constructor should only be used to load the "core" findbugs detectors,
* which are built into findbugs.jar.
* @throws PluginException
*/
@Deprecated
public PluginLoader() {
classLoader = getClass().getClassLoader();
classLoaderForResources = classLoader;
corePlugin = true;
initialPlugin = true;
optionalPlugin = false;
loadedFrom = computeCoreUrl();
try {
loadedFromUri = loadedFrom.toURI();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Failed to parse uri: " + loadedFrom);
}
jarName = getJarName(loadedFrom);
}
private URL computeCoreUrl() {
URL from;
String findBugsClassFile = ClassName.toSlashedClassName(FindBugs.class) + ".class";
URL me = FindBugs.class.getClassLoader().getResource(findBugsClassFile);
if (DEBUG)
System.out.println("FindBugs.class loaded from " + me);
if(me == null) {
throw new IllegalStateException("Failed to load " + findBugsClassFile);
}
try {
String u = me.toString();
if (u.startsWith("jar:") && u.endsWith("!/" + findBugsClassFile)) {
u = u.substring(4, u.indexOf("!/"));
from = new URL(u);
} else if (u.endsWith(findBugsClassFile)) {
u = u.substring(0, u.indexOf(findBugsClassFile));
from = new URL(u);
} else {
throw new IllegalArgumentException("Unknown url shema: " + u);
}
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Failed to parse url: " + me);
}
if (DEBUG)
System.out.println("Core class files loaded from " + from);
return from;
}
public URL getURL() {
return loadedFrom;
}
private static URI toUri(URL url) throws PluginException {
try {
return url.toURI();
} catch (URISyntaxException e) {
throw new PluginException("Bad uri: " + url, e);
}
}
/**
* @param url
* @return
*/
private String getJarName(URL url) {
String location = url.getPath();
int i = location.lastIndexOf("/");
location = location.substring(i + 1);
return location;
}
/**
* @return Returns the classLoader.
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Get the Plugin.
*
* @throws PluginException
* if the plugin cannot be fully loaded
*/
public synchronized Plugin loadPlugin() throws PluginException {
if (plugin == null) {
plugin = init();
}
return plugin;
}
/**
* Get the Plugin.
*
*/
public Plugin getPlugin() {
if (plugin == null)
throw new AssertionError("plugin not already loaded");
return plugin;
}
private static URL resourceFromPlugin(URL u, String args) throws MalformedURLException {
String path = u.getPath();
if (path.endsWith(".zip") || path.endsWith(".jar")) {
return new URL("jar:" + u.toString() + "!/" + args);
} else if (path.endsWith("/")) {
return new URL(u.toString() + "" + args);
} else {
return new URL(u.toString() + "/" + args);
}
}
/**
* Get a resource using the URLClassLoader classLoader. We try findResource
* first because (based on experiment) we can trust it to prefer resources
* in the jarfile to resources on the filesystem. Simply calling
* classLoader.getResource() allows the filesystem to override the jarfile,
* which can mess things up if, for example, there is a findbugs.xml or
* messages.xml in the current directory.
*
* @param name
* resource to get
* @return URL for the resource, or null if it could not be found
*/
public URL getResource(String name) {
if (isCorePlugin()) {
URL url = getCoreResource(name);
if (url != null && IO.verifyURL(url))
return url;
}
if (loadedFrom != null) {
try {
URL url = resourceFromPlugin(loadedFrom, name);
if (DEBUG) {
System.out.println("Trying to load " + name + " from " + url);
}
if (IO.verifyURL(url))
return url;
} catch (MalformedURLException e) {
assert true;
}
}
if (classLoaderForResources instanceof URLClassLoader) {
URLClassLoader urlClassLoader = (URLClassLoader) classLoaderForResources;
if (DEBUG) {
System.out.println("Trying to load " + name + " using URLClassLoader.findResource");
System.out.println(" from urls: " + Arrays.asList(urlClassLoader.getURLs()));
}
URL url = urlClassLoader.findResource(name);
if (url == null)
url = urlClassLoader.findResource("/" + name);
if (IO.verifyURL(url))
return url;
}
if (DEBUG) {
System.out.println("Trying to load " + name + " using ClassLoader.getResource");
}
URL url = classLoaderForResources.getResource(name);
if (url == null)
url = classLoaderForResources.getResource("/" + name);
if (IO.verifyURL(url))
return url;
return null;
}
static @CheckForNull
URL getCoreResource(String name) {
URL u = loadFromFindBugsPluginDir(name);
if (u != null)
return u;
u = loadFromFindBugsEtcDir(name);
if (u != null)
return u;
u = PluginLoader.class.getResource(name);
if (u != null)
return u;
u = PluginLoader.class.getResource("/"+name);
return u;
}
public static @CheckForNull
URL loadFromFindBugsEtcDir(String name) {
String findBugsHome = DetectorFactoryCollection.getFindBugsHome();
if (findBugsHome != null) {
File f = new File(new File(new File(findBugsHome), "etc"), name);
if (f.canRead())
try {
return f.toURL();
} catch (MalformedURLException e) {
// ignore it
}
}
return null;
}
public static @CheckForNull
URL loadFromFindBugsPluginDir(String name) {
String findBugsHome = DetectorFactoryCollection.getFindBugsHome();
if (findBugsHome != null) {
File f = new File(new File(new File(findBugsHome), "plugin"), name);
if (f.canRead())
try {
return f.toURI().toURL();
} catch (MalformedURLException e) {
// ignore it
}
}
return null;
}
private static <T> Class<? extends T> getClass(ClassLoader loader, String className, Class<T> type) throws PluginException {
try {
return loader.loadClass(className).asSubclass(type);
} catch (ClassNotFoundException e) {
throw new PluginException("Unable to load " + className, e);
} catch (ClassCastException e) {
throw new PluginException("Cannot cast " + className + " to " + type.getName(), e);
}
}
private Plugin init() throws PluginException {
if (DEBUG)
System.out.println("Loading plugin from " + loadedFrom);
// Plugin descriptor (a.k.a, "findbugs.xml"). Defines
// the bug detectors and bug patterns that the plugin provides.
Document pluginDescriptor;
// Unique plugin id
String pluginId;
// List of message translation files in decreasing order of precedence
ArrayList<Document> messageCollectionList = new ArrayList<Document>();
// Read the plugin descriptor
String name = "findbugs.xml";
URL findbugsXML_URL = getResource(name);
if (findbugsXML_URL == null)
throw new PluginException("Couldn't find \"" + name + "\" in plugin");
if (DEBUG)
System.out.println("PluginLoader found " + name + " at: " + findbugsXML_URL);
if (jarName != null && !findbugsXML_URL.toString().contains(jarName)
&& !(corePlugin && findbugsXML_URL.toString().endsWith("etc/findbugs.xml"))) {
String classloaderName = classLoader.getClass().getName();
if (classLoader instanceof URLClassLoader) {
classloaderName += Arrays.asList(((URLClassLoader) classLoader).getURLs());
}
throw new PluginDoesntContainMetadataException((corePlugin ? "Core plugin" : "Plugin ") + jarName
+ " doesn't contain findbugs.xml; got " + findbugsXML_URL + " from " + classloaderName);
}
SAXReader reader = new SAXReader();
try {
Reader r = UTF8.bufferedReader(findbugsXML_URL.openStream());
pluginDescriptor = reader.read(r);
} catch (DocumentException e) {
throw new PluginException("Couldn't parse \"" + findbugsXML_URL + "\" using " + reader.getClass().getName(), e);
} catch (IOException e) {
throw new PluginException("Couldn't open \"" + findbugsXML_URL + "\"", e);
}
// Get the unique plugin id (or generate one, if none is present)
pluginId = pluginDescriptor.valueOf("/FindbugsPlugin/@pluginid");
if (pluginId.equals("")) {
synchronized (PluginLoader.class) {
pluginId = "plugin" + nextUnknownId++;
}
}
if (!loadedPluginIds.add(pluginId)) {
Plugin existingPlugin = Plugin.getByPluginId(pluginId);
if (existingPlugin == null)
throw new DuplicatePluginIdDescriptor(pluginId, loadedFrom, null);
throw new DuplicatePluginIdDescriptor(pluginId, loadedFrom, existingPlugin.getPluginLoader().getURL());
}
String version = pluginDescriptor.valueOf("/FindbugsPlugin/@version");
// Load the message collections
Locale locale = Locale.getDefault();
String language = locale.getLanguage();
String country = locale.getCountry();
try {
if (country != null)
addCollection(messageCollectionList, "messages_" + language + "_" + country + ".xml");
addCollection(messageCollectionList, "messages_" + language + ".xml");
} catch (PluginException e) {
AnalysisContext.logError("Error loading localized message file", e);
}
addCollection(messageCollectionList, "messages.xml");
// Create the Plugin object (but don't assign to the plugin field yet,
// since we're still not sure if everything will load correctly)
Plugin plugin = new Plugin(pluginId, version, this, !optionalPlugin);
// Set provider and website, if specified
String provider = pluginDescriptor.valueOf("/FindbugsPlugin/@provider").trim();
if (!provider.equals(""))
plugin.setProvider(provider);
String website = pluginDescriptor.valueOf("/FindbugsPlugin/@website").trim();
if (!website.equals(""))
try {
plugin.setWebsite(website);
} catch (URISyntaxException e1) {
AnalysisContext.logError("Plugin " + pluginId + " has invalid website: " + website, e1);
}
String usageTracker = pluginDescriptor.valueOf("/FindbugsPlugin/@usageTracker").trim();
if (!usageTracker.equals(""))
try {
plugin.setUsageTracker(usageTracker);
} catch (URISyntaxException e1) {
AnalysisContext.logError("Plugin " + pluginId + " has invalid usageTracker: " + website, e1);
}
// Set short description, if specified
Node pluginShortDesc = null;
try {
pluginShortDesc = findMessageNode(messageCollectionList, "/MessageCollection/Plugin/ShortDescription",
"no plugin description");
} catch (PluginException e) {
// Missing description is not fatal, so ignore
}
if (pluginShortDesc != null) {
plugin.setShortDescription(pluginShortDesc.getText().trim());
}
Node detailedDescription = null;
try {
detailedDescription = findMessageNode(messageCollectionList, "/MessageCollection/Plugin/Details",
"no plugin description");
} catch (PluginException e) {
// Missing description is not fatal, so ignore
}
if (detailedDescription != null) {
plugin.setDetailedDescription(detailedDescription.getText().trim());
}
List<Node> globalOptionNodes = pluginDescriptor.selectNodes("/FindbugsPlugin/GlobalOptions/Property");
for(Node optionNode : globalOptionNodes) {
String key = optionNode.valueOf("@key");
String value = optionNode.getText();
String oldValue = Plugin.globalOptions.get(key);
if (oldValue != null) {
if (!oldValue.equals(value)) {
throw new PluginException("Conflicting values for global option " + key + " from "
+ pluginId + " and " + Plugin.globalOptionsSetter.get(key).getPluginId());
}
} else {
Plugin.globalOptions.put(key, value);
Plugin.globalOptionsSetter.put(key, plugin);
}
}
List<Node> cloudNodeList = pluginDescriptor.selectNodes("/FindbugsPlugin/Cloud");
for (Node cloudNode : cloudNodeList) {
String cloudClassname = cloudNode.valueOf("@cloudClass");
String cloudId = cloudNode.valueOf("@id");
String usernameClassname = cloudNode.valueOf("@usernameClass");
boolean onlineStorage = Boolean.valueOf(cloudNode.valueOf("@onlineStorage"));
String propertiesLocation = cloudNode.valueOf("@properties");
boolean disabled = Boolean.valueOf(cloudNode.valueOf("@disabled")) && !cloudId.equals(CloudFactory.DEFAULT_CLOUD);
if (disabled)
continue;
boolean hidden = Boolean.valueOf(cloudNode.valueOf("@hidden")) && !cloudId.equals(CloudFactory.DEFAULT_CLOUD);
Class<? extends Cloud> cloudClass = getClass(classLoader, cloudClassname, Cloud.class);
Class<? extends NameLookup> usernameClass = getClass(classLoader, usernameClassname, NameLookup.class);
Node cloudMessageNode = findMessageNode(messageCollectionList, "/MessageCollection/Cloud[@id='" + cloudId + "']",
"Missing Cloud description for cloud " + cloudId);
String description = getChildText(cloudMessageNode, "Description").trim();
String details = getChildText(cloudMessageNode, "Details").trim();
PropertyBundle properties = new PropertyBundle();
if (propertiesLocation != null && propertiesLocation.length() > 0) {
URL properiesURL = classLoader.getResource(propertiesLocation);
if (properiesURL == null)
continue;
properties.loadPropertiesFromURL(properiesURL);
}
List<Node> propertyNodes = cloudNode.selectNodes("Property");
for (Node node : propertyNodes) {
String key = node.valueOf("@key");
String value = node.getText();
properties.setProperty(key, value);
}
CloudPlugin cloudPlugin = new CloudPluginBuilder().setFindbugsPluginId(pluginId).setCloudid(cloudId).setClassLoader(classLoader)
.setCloudClass(cloudClass).setUsernameClass(usernameClass).setHidden(hidden).setProperties(properties)
.setDescription(description).setDetails(details).setOnlineStorage(onlineStorage).createCloudPlugin();
plugin.addCloudPlugin(cloudPlugin);
}
// Create PluginComponents
try {
List<Node> filterNodeList = pluginDescriptor.selectNodes("/FindbugsPlugin/PluginComponent");
for (Node filterNode : filterNodeList) {
String componentKindname = filterNode.valueOf("@componentKind");
if (componentKindname == null) throw new PluginException("Missing @componentKind for " + pluginId
+ " loaded from " + loadedFrom);
String componentClassname = filterNode.valueOf("@componentClass");
if (componentClassname == null) throw new PluginException("Missing @componentClassname for " + pluginId
+ " loaded from " + loadedFrom);
String filterId = filterNode.valueOf("@id");
if (filterId == null) throw new PluginException("Missing @id for " + pluginId
+ " loaded from " + loadedFrom);
try {
String propertiesLocation = filterNode.valueOf("@properties");
boolean disabled = Boolean.valueOf(filterNode.valueOf("@disabled"));
Class<?> componentKind = classLoader.loadClass(componentKindname);
Class<?> componentClass = null;
if (!FindBugs.noAnalysis) {
componentClass = getClass(classLoader, componentClassname, componentKind);
}
Node filterMessageNode = findMessageNode(messageCollectionList,
"/MessageCollection/PluginComponent[@id='" + filterId + "']",
"Missing Cloud description for PluginComponent " + filterId);
String description = getChildText(filterMessageNode, "Description").trim();
String details = getChildText(filterMessageNode, "Details").trim();
PropertyBundle properties = new PropertyBundle();
if (propertiesLocation != null && propertiesLocation.length() > 0) {
URL properiesURL = classLoaderForResources.getResource(propertiesLocation);
if (properiesURL == null)
continue;
properties.loadPropertiesFromURL(properiesURL);
}
List<Node> propertyNodes = filterNode.selectNodes("Property");
for (Node node : propertyNodes) {
String key = node.valueOf("@key");
String value = node.getText();
properties.setProperty(key, value);
}
ComponentPlugin componentPlugin = new ComponentPlugin(plugin, filterId, classLoader, componentClass,
properties, !disabled, description, details);
plugin.addComponentPlugin(componentKind, componentPlugin);
} catch (RuntimeException e) {
AnalysisContext.logError("Unable to load ComponentPlugin " + filterId +
" : " + componentClassname + " implementing " + componentKindname, e);
}
}
// Create FindBugsMains
List<Node> findBugsMainList = pluginDescriptor.selectNodes("/FindbugsPlugin/FindBugsMain");
for (Node main : findBugsMainList) {
String className = main.valueOf("@class");
if (className == null) throw new PluginException("Missing @class for FindBugsMain in plugin" + pluginId
+ " loaded from " + loadedFrom);
String cmd = main.valueOf("@cmd");
if (cmd == null) throw new PluginException("Missing @cmd for for FindBugsMain in plugin " + pluginId
+ " loaded from " + loadedFrom);
String kind = main.valueOf("@kind");
boolean analysis = Boolean.valueOf(main.valueOf("@analysis"));
try {
Class<?> mainClass = classLoader.loadClass(className);
plugin.addFindBugsMain(mainClass, cmd, kind, analysis);
} catch (Exception e) {
AnalysisContext.logError("Unable to load FindBugsMain " + cmd +
" : " + className + " in plugin " + pluginId
+ " loaded from " + loadedFrom, e);
}
}
List<Node> detectorNodeList = pluginDescriptor.selectNodes("/FindbugsPlugin/Detector");
int detectorCount = 0;
for (Node detectorNode : detectorNodeList) {
String className = detectorNode.valueOf("@class");
String speed = detectorNode.valueOf("@speed");
String disabled = detectorNode.valueOf("@disabled");
String reports = detectorNode.valueOf("@reports");
String requireJRE = detectorNode.valueOf("@requirejre");
String hidden = detectorNode.valueOf("@hidden");
if (speed == null || speed.length() == 0)
speed = "fast";
// System.out.println("Found detector: class="+className+", disabled="+disabled);
// Create DetectorFactory for the detector
Class<?> detectorClass = null;
if (!FindBugs.noAnalysis) {
detectorClass = classLoader.loadClass(className);
if (!Detector.class.isAssignableFrom(detectorClass) && !Detector2.class.isAssignableFrom(detectorClass))
throw new PluginException("Class " + className + " does not implement Detector or Detector2");
}
DetectorFactory factory = new DetectorFactory(plugin, className, detectorClass, !disabled.equals("true"), speed,
reports, requireJRE);
if (Boolean.valueOf(hidden).booleanValue())
factory.setHidden(true);
factory.setPositionSpecifiedInPluginDescriptor(detectorCount++);
plugin.addDetectorFactory(factory);
// Find Detector node in one of the messages files,
// to get the detail HTML.
Node node = findMessageNode(messageCollectionList, "/MessageCollection/Detector[@class='" + className
+ "']/Details", "Missing Detector description for detector " + className);
Element details = (Element) node;
String detailHTML = details.getText();
StringBuilder buf = new StringBuilder();
buf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n");
buf.append("<HTML><HEAD><TITLE>Detector Description</TITLE></HEAD><BODY>\n");
buf.append(detailHTML);
buf.append("</BODY></HTML>\n");
factory.setDetailHTML(buf.toString());
}
} catch (ClassNotFoundException e) {
throw new PluginException("Could not instantiate detector class: " + e, e);
}
// Create ordering constraints
Node orderingConstraintsNode = pluginDescriptor.selectSingleNode("/FindbugsPlugin/OrderingConstraints");
if (orderingConstraintsNode != null) {
// Get inter-pass and intra-pass constraints
for (Element constraintElement : (List<Element>) orderingConstraintsNode.selectNodes("./SplitPass|./WithinPass")) {
// Create the selectors which determine which detectors are
// involved in the constraint
DetectorFactorySelector earlierSelector = getConstraintSelector(constraintElement, plugin, "Earlier");
DetectorFactorySelector laterSelector = getConstraintSelector(constraintElement, plugin, "Later");
// Create the constraint
DetectorOrderingConstraint constraint = new DetectorOrderingConstraint(earlierSelector, laterSelector);
// Keep track of which constraints are single-source
constraint.setSingleSource(earlierSelector instanceof SingleDetectorFactorySelector);
// Add the constraint to the plugin
if (constraintElement.getName().equals("SplitPass"))
plugin.addInterPassOrderingConstraint(constraint);
else
plugin.addIntraPassOrderingConstraint(constraint);
}
}
// register global Category descriptions
for (Document messageCollection : messageCollectionList) {
List<Node> categoryNodeList = messageCollection.selectNodes("/MessageCollection/BugCategory");
if (DEBUG)
System.out.println("found " + categoryNodeList.size() + " categories in " + pluginId);
for (Node categoryNode : categoryNodeList) {
String key = categoryNode.valueOf("@category");
if (key.equals(""))
throw new PluginException("BugCategory element with missing category attribute");
String shortDesc = getChildText(categoryNode, "Description");
BugCategory bc = new BugCategory(key, shortDesc);
try {
String abbrev = getChildText(categoryNode, "Abbreviation");
if (bc.getAbbrev() == null) {
bc.setAbbrev(abbrev);
if (DEBUG)
System.out.println("category " + key + " abbrev -> " + abbrev);
} else if (DEBUG)
System.out.println("rejected abbrev '" + abbrev + "' for category " + key + ": " + bc.getAbbrev());
} catch (PluginException pe) {
if (DEBUG)
System.out.println("missing Abbreviation for category " + key + "/" + shortDesc);
// do nothing else -- Abbreviation is required, but handle
// its omission gracefully
}
try {
String details = getChildText(categoryNode, "Details");
if (bc.getDetailText() == null) {
bc.setDetailText(details);
if (DEBUG)
System.out.println("category " + key + " details -> " + details);
} else if (DEBUG)
System.out.println("rejected details [" + details + "] for category " + key + ": [" + bc.getDetailText()
+ ']');
} catch (PluginException pe) {
// do nothing -- LongDescription is optional
}
plugin.addBugCategory(bc);
}
}
// Create BugPatterns
List<Node> bugPatternNodeList = pluginDescriptor.selectNodes("/FindbugsPlugin/BugPattern");
for (Node bugPatternNode : bugPatternNodeList) {
String type = bugPatternNode.valueOf("@type");
String abbrev = bugPatternNode.valueOf("@abbrev");
String category = bugPatternNode.valueOf("@category");
String experimental = bugPatternNode.valueOf("@experimental");
// Find the matching element in messages.xml (or translations)
String query = "/MessageCollection/BugPattern[@type='" + type + "']";
Node messageNode = findMessageNode(messageCollectionList, query, "messages.xml missing BugPattern element for type "
+ type);
String shortDesc = getChildText(messageNode, "ShortDescription");
String longDesc = getChildText(messageNode, "LongDescription");
String detailText = getChildText(messageNode, "Details");
int cweid = 0;
try {
String cweString = bugPatternNode.valueOf("@cweid");
if (cweString.length() > 0)
cweid = Integer.parseInt(cweString);
} catch (RuntimeException e) {
assert true; // ignore
}
BugPattern bugPattern = new BugPattern(type, abbrev, category, Boolean.valueOf(experimental).booleanValue(),
shortDesc, longDesc, detailText, cweid);
try {
String deprecatedStr = bugPatternNode.valueOf("@deprecated");
boolean deprecated = deprecatedStr.length() > 0 && Boolean.valueOf(deprecatedStr).booleanValue();
if (deprecated) {
bugPattern.setDeprecated(deprecated);
}
} catch (RuntimeException e) {
assert true; // ignore
}
plugin.addBugPattern(bugPattern);
}
// Create BugCodes
Set<String> definedBugCodes = new HashSet<String>();
for (Document messageCollection : messageCollectionList) {
List<Node> bugCodeNodeList = messageCollection.selectNodes("/MessageCollection/BugCode");
for (Node bugCodeNode : bugCodeNodeList) {
String abbrev = bugCodeNode.valueOf("@abbrev");
if (abbrev.equals(""))
throw new PluginException("BugCode element with missing abbrev attribute");
if (definedBugCodes.contains(abbrev))
continue;
String description = bugCodeNode.getText();
String query = "/FindbugsPlugin/BugCode[@abbrev='" + abbrev + "']";
Node fbNode = pluginDescriptor.selectSingleNode(query);
int cweid = 0;
if (fbNode != null)
try {
cweid = Integer.parseInt(fbNode.valueOf("@cweid"));
} catch (RuntimeException e) {
assert true; // ignore
}
BugCode bugCode = new BugCode(abbrev, description, cweid);
plugin.addBugCode(bugCode);
definedBugCodes.add(abbrev);
}
}
// If an engine registrar is specified, make a note of its classname
Node node = pluginDescriptor.selectSingleNode("/FindbugsPlugin/EngineRegistrar");
if (node != null) {
String engineClassName = node.valueOf("@class");
if (engineClassName == null) {
throw new PluginException("EngineRegistrar element with missing class attribute");
}
try {
Class<?> engineRegistrarClass = classLoader.loadClass(engineClassName);
if (!IAnalysisEngineRegistrar.class.isAssignableFrom(engineRegistrarClass)) {
throw new PluginException(engineRegistrarClass + " does not implement IAnalysisEngineRegistrar");
}
plugin.setEngineRegistrarClass(engineRegistrarClass
.<IAnalysisEngineRegistrar> asSubclass(IAnalysisEngineRegistrar.class));
} catch (ClassNotFoundException e) {
throw new PluginException("Could not instantiate analysis engine registrar class: " + e, e);
}
}
try {
URL bugRankURL = getResource(BugRanker.FILENAME);
if (bugRankURL == null) {
// see
// https://sourceforge.net/tracker/?func=detail&aid=2816102&group_id=96405&atid=614693
// plugin can not have bugrank.txt. In this case, an empty
// bugranker will be created
if (DEBUG)
System.out.println("No " + BugRanker.FILENAME + " for plugin " + pluginId);
}
BugRanker ranker = new BugRanker(bugRankURL);
plugin.setBugRanker(ranker);
} catch (IOException e) {
throw new PluginException("Couldn't parse \"" + BugRanker.FILENAME + "\"", e);
}
new UsageTracker().trackUsage(plugin.getUsageTracker(), Collections.singleton(plugin));
// Success!
if (DEBUG)
System.out.println("Loaded " + plugin.getPluginId() + " from " + loadedFrom);
return plugin;
}
private static DetectorFactorySelector getConstraintSelector(Element constraintElement, Plugin plugin,
String singleDetectorElementName/*
* , String
* detectorCategoryElementName
*/) throws PluginException {
Node node = constraintElement.selectSingleNode("./" + singleDetectorElementName);
if (node != null) {
String detectorClass = node.valueOf("@class");
return new SingleDetectorFactorySelector(plugin, detectorClass);
}
node = constraintElement.selectSingleNode("./" + singleDetectorElementName + "Category");
if (node != null) {
boolean spanPlugins = Boolean.valueOf(node.valueOf("@spanplugins")).booleanValue();
String categoryName = node.valueOf("@name");
if (!categoryName.equals("")) {
if (categoryName.equals("reporting")) {
return new ReportingDetectorFactorySelector(spanPlugins ? null : plugin);
} else if (categoryName.equals("training")) {
return new ByInterfaceDetectorFactorySelector(spanPlugins ? null : plugin, TrainingDetector.class);
} else if (categoryName.equals("interprocedural")) {
return new ByInterfaceDetectorFactorySelector(spanPlugins ? null : plugin,
InterproceduralFirstPassDetector.class);
} else {
throw new PluginException("Invalid category name " + categoryName + " in constraint selector node");
}
}
}
node = constraintElement.selectSingleNode("./" + singleDetectorElementName + "Subtypes");
if (node != null) {
boolean spanPlugins = Boolean.valueOf(node.valueOf("@spanplugins")).booleanValue();
String superName = node.valueOf("@super");
if (!superName.equals("")) {
try {
Class<?> superClass = Class.forName(superName);
return new ByInterfaceDetectorFactorySelector(spanPlugins ? null : plugin, superClass);
} catch (ClassNotFoundException e) {
throw new PluginException("Unknown class " + superName + " in constraint selector node");
}
}
}
throw new PluginException("Invalid constraint selector node");
}
private void addCollection(List<Document> messageCollectionList, String filename) throws PluginException {
URL messageURL = getResource(filename);
if (messageURL != null) {
SAXReader reader = new SAXReader();
try {
Reader stream = UTF8.bufferedReader(messageURL.openStream());
Document messageCollection;
try {
messageCollection = reader.read(stream);
} finally {
stream.close();
}
messageCollectionList.add(messageCollection);
} catch (Exception e) {
throw new PluginException("Couldn't parse \"" + messageURL + "\"", e);
} finally {
}
}
}
private static Node findMessageNode(List<Document> messageCollectionList, String xpath, String missingMsg)
throws PluginException {
for (Document document : messageCollectionList) {
Node node = document.selectSingleNode(xpath);
if (node != null)
return node;
}
throw new PluginException(missingMsg);
}
private static String getChildText(Node node, String childName) throws PluginException {
Node child = node.selectSingleNode(childName);
if (child == null)
throw new PluginException("Could not find child \"" + childName + "\" for node");
return child.getText();
}
/**
* @deprecated Use {@link #getPluginLoader(URL,ClassLoader,boolean,boolean)} instead
*/
public static PluginLoader getPluginLoader(URL url, ClassLoader parent) throws PluginException {
return getPluginLoader(url, parent, false, true);
}
public static PluginLoader getPluginLoader(URL url, ClassLoader parent, boolean isInitial, boolean optional) throws PluginException {
URI uri = toUri(url);
Plugin plugin = Plugin.getPlugin(uri);
if (plugin != null) {
PluginLoader loader = plugin.getPluginLoader();
assert loader.getClassLoader().getParent().equals(parent);
return loader;
}
return new PluginLoader(url, uri, parent, isInitial, optional);
}
@Nonnull
public static synchronized PluginLoader getCorePluginLoader() {
Plugin plugin = Plugin.getPlugin(null);
if (plugin != null) {
return plugin.getPluginLoader();
}
throw new IllegalStateException("Core plugin not loaded yet!");
}
public boolean isCorePlugin() {
return corePlugin;
}
static void installStandardPlugins() {
String homeDir = DetectorFactoryCollection.getFindBugsHome();
if (homeDir == null)
return;
File home = new File(homeDir);
loadPlugins(home);
}
private static void loadPlugins(File home) {
if (home.canRead() && home.isDirectory()) {
loadPluginsInDir(new File(home, "plugin"), false);
loadPluginsInDir(new File(home, "optionalPlugin"), true);
}
}
static void installUserInstalledPlugins() {
String homeDir = System.getProperty("user.home");
if (homeDir == null)
return;
File homeFindBugs = new File(new File(homeDir), ".findbugs");
loadPlugins(homeFindBugs);
}
private static void loadPluginsInDir(File pluginDir, boolean optional) {
File[] contentList = pluginDir.listFiles();
if (contentList == null) {
return;
}
for (File file : contentList) {
if (file.getName().endsWith(".jar")) {
try {
URL url = file.toURI().toURL();
if (IO.verifyURL(url)) {
loadInitialPlugin(url, true, optional);
if (FindBugs.DEBUG)
System.out.println("Found plugin: " + file.toString());
}
} catch (MalformedURLException e) {
}
}
}
}
static synchronized void loadInitialPlugins() {
loadCorePlugin();
if (JavaWebStart.isRunningViaJavaWebstart()) {
installWebStartPlugins();
} else {
installStandardPlugins();
installUserInstalledPlugins();
}
Set<Entry<Object, Object>> entrySet = SystemProperties.getAllProperties().entrySet();
for (Map.Entry<?, ?> e : entrySet) {
if (e.getKey() instanceof String && e.getValue() instanceof String
&& ((String) e.getKey()).startsWith("findbugs.plugin.")) {
try {
String value = (String) e.getValue();
if (value.startsWith("file:") && !value.endsWith(".jar") && !value.endsWith("/"))
value += "/";
URL url = JavaWebStart.resolveRelativeToJnlpCodebase(value);
loadInitialPlugin(url, true, false);
} catch (MalformedURLException e1) {
AnalysisContext.logError(String.format("Bad URL for plugin: %s=%s", e.getKey(), e.getValue()), e1);
}
}
}
if (Plugin.getAllPlugins().size() > 1 && JavaWebStart.isRunningViaJavaWebstart()) {
// disable security manager; plugins cause problems
// http://lopica.sourceforge.net/faq.html
// URL policyUrl =
// Thread.currentThread().getContextClassLoader().getResource("my.java.policy");
// Policy.getPolicy().refresh();
try {
System.setSecurityManager(null);
} catch (Throwable e) {
assert true; // keep going
}
}
}
private static void loadCorePlugin() {
try {
Plugin plugin = Plugin.getPlugin(null);
if (plugin != null) {
throw new IllegalStateException("Already loaded");
}
PluginLoader pluginLoader = new PluginLoader();
plugin = pluginLoader.loadPlugin();
Plugin.putPlugin(null, plugin);
} catch (PluginException e1) {
throw new IllegalStateException("Unable to load core plugin", e1);
}
}
private static void loadInitialPlugin(URL u, boolean initial, boolean optional) {
try {
PluginLoader pluginLoader = getPluginLoader(u, PluginLoader.class.getClassLoader(), initial, optional);
pluginLoader.loadPlugin();
} catch (PluginException e) {
AnalysisContext.logError("Unable to load plugin from " + u, e);
}
}
/**
* @param plugins
*/
static void installWebStartPlugins() {
URL pluginListProperties = getCoreResource("pluginlist.properties");
BufferedReader in = null;
if (pluginListProperties != null) {
try {
DetectorFactoryCollection.jawsDebugMessage(pluginListProperties.toString());
URL base = getUrlBase(pluginListProperties);
in = UTF8.bufferedReader(pluginListProperties.openStream());
while (true) {
String plugin = in.readLine();
if (plugin == null)
break;
URL url = new URL(base, plugin);
try {
URLConnection connection = url.openConnection();
String contentType = connection.getContentType();
DetectorFactoryCollection.jawsDebugMessage("contentType : " + contentType);
if (connection instanceof HttpURLConnection)
((HttpURLConnection) connection).disconnect();
loadInitialPlugin(url, true, false);
} catch (Exception e) {
DetectorFactoryCollection.jawsDebugMessage("error loading " + url + " : " + e.getMessage());
}
}
} catch (Exception e) {
DetectorFactoryCollection.jawsDebugMessage("error : " + e.getMessage());
} finally {
Util.closeSilently(in);
}
}
}
/**
* @param pluginListProperties
* @return
* @throws MalformedURLException
*/
private static URL getUrlBase(URL pluginListProperties) throws MalformedURLException {
String urlname = pluginListProperties.toString();
URL base = pluginListProperties;
int pos = urlname.indexOf("!/");
if (pos >= 0 && urlname.startsWith("jar:")) {
urlname = urlname.substring(4, pos);
base = new URL(urlname);
}
return base;
}
@Override
public String toString() {
if (plugin == null)
return String.format("PluginLoader(%s)", loadedFrom);
return String.format("PluginLoader(%s, %s)", plugin.getPluginId(), loadedFrom);
}
}