edu.umd.cs.findbugs.PluginLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* 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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
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.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.WillClose;
import edu.umd.cs.findbugs.util.SecurityManagerHandler;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.internalAnnotations.DottedClassName;
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.DuplicatePluginIdError;
import edu.umd.cs.findbugs.plugins.DuplicatePluginIdException;
import edu.umd.cs.findbugs.util.ClassName;
import edu.umd.cs.findbugs.util.JavaWebStart;
import edu.umd.cs.findbugs.util.Util;
import edu.umd.cs.findbugs.xml.XMLUtil;
/**
* Loader for a FindBugs plugin. A plugin is a jar file containing two metadata
* files, "findbugs.xml" and "messages.xml". Those files specify
*
* - the bug pattern Detector classes,
*
- the bug patterns detected (including all text for displaying detected
* instances of those patterns), and
*
- the "bug codes" which group together related bug instances
*
*
*
* The PluginLoader creates a Plugin object to store the Detector factories and
* metadata.
*
*
* @author David Hovemeyer
* @see Plugin
* @see PluginException
*/
public class PluginLoader implements AutoCloseable {
private static final String XPATH_PLUGIN_SHORT_DESCRIPTION = "/MessageCollection/Plugin/ShortDescription";
private static final String XPATH_PLUGIN_WEBSITE = "/FindbugsPlugin/@website";
private static final String XPATH_PLUGIN_PROVIDER = "/FindbugsPlugin/@provider";
private static final String XPATH_PLUGIN_PLUGINID = "/FindbugsPlugin/@pluginid";
private static final Logger LOG = LoggerFactory.getLogger(PluginLoader.class);
static boolean lazyInitialization = false;
static LinkedList partiallyInitialized = new LinkedList<>();
// 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;
// ClassLoader used to load classes and resources
private ClassLoader classLoader;
private final ClassLoader classLoaderForResources;
// The loaded Plugin
private final Plugin plugin;
private final boolean corePlugin;
boolean initialPlugin;
boolean cannotDisable;
private boolean optionalPlugin;
private final URL loadedFrom;
private final String jarName;
private final URI loadedFromUri;
// The classloaders we have created, so we can close then if we want to close the plugin
private List createdClassLoaders = new ArrayList<>();
/** plugin Id for parent plugin */
String parentId;
static HashSet loadedPluginIds = new HashSet<>();
static {
LOG.debug("Debugging plugin loading. SpotBugs version {}", Version.VERSION_STRING);
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,URI,ClassLoader,boolean,boolean)} instead
*/
@Deprecated
public PluginLoader(URL url, ClassLoader parent) throws PluginException {
this(url, toUri(url), parent, false, true);
}
public boolean hasParent() {
return parentId != null && parentId.length() > 0;
}
/**
* Constructor.
*
* @param url
* the URL of the plugin Jar file
* @param uri
* @param parent
* the parent classloader
* @param isInitial
* is this plugin loaded from one of the standard locations for plugins
* @param optional
* is this an optional plugin
*/
private PluginLoader(@Nonnull URL url, URI uri, ClassLoader parent, boolean isInitial, boolean optional) throws PluginException {
URL[] loaderURLs = createClassloaderUrls(url);
classLoaderForResources = buildURLClassLoader(loaderURLs);
loadedFrom = url;
loadedFromUri = uri;
jarName = getJarName(url);
corePlugin = false;
initialPlugin = isInitial;
optionalPlugin = optional;
plugin = init();
if (!hasParent()) {
classLoader = buildURLClassLoader(loaderURLs, parent);
} else {
if (parent != PluginLoader.class.getClassLoader()) {
throw new IllegalArgumentException("Can't specify parentid " + parentId + " and provide a separate class loader");
}
Plugin parentPlugin = Plugin.getByPluginId(parentId);
if (parentPlugin != null) {
parent = parentPlugin.getClassLoader();
classLoader = buildURLClassLoader(loaderURLs, parent);
}
}
if (classLoader == null) {
if (!lazyInitialization) {
throw new IllegalStateException("Can't find parent plugin " + parentId);
}
partiallyInitialized.add(this);
} else {
loadPluginComponents();
Plugin.putPlugin(loadedFromUri, plugin);
}
}
private static void finishLazyInitialization() {
if (!lazyInitialization) {
throw new IllegalStateException("Not in lazy initialization mode");
}
while (!partiallyInitialized.isEmpty()) {
boolean changed = false;
LinkedList unresolved = new LinkedList<>();
Set needed = new TreeSet<>();
for (Iterator i = partiallyInitialized.iterator(); i.hasNext();) {
PluginLoader pluginLoader = i.next();
String pluginId = pluginLoader.getPlugin().getPluginId();
assert pluginLoader.hasParent();
String parentid = pluginLoader.parentId;
Plugin parent = Plugin.getByPluginId(parentid);
if (parent != null) {
i.remove();
try {
URL[] loaderURLs = PluginLoader.createClassloaderUrls(pluginLoader.loadedFrom);
pluginLoader.classLoader = pluginLoader.buildURLClassLoader(loaderURLs, parent.getClassLoader());
pluginLoader.loadPluginComponents();
Plugin.putPlugin(pluginLoader.loadedFromUri, pluginLoader.plugin);
} catch (PluginException e) {
throw new RuntimeException("Unable to load plugin " + pluginId, e);
}
changed = true;
} else {
unresolved.add(pluginId);
needed.add(parentid);
}
}
if (!changed) {
String msg = "Unable to load parent plugins " + needed + " in order to load " + unresolved;
System.err.println(msg);
AnalysisContext.logError(msg);
msg = "Available plugins are " + Plugin.getAllPluginIds();
System.err.println(msg);
AnalysisContext.logError(msg);
for (PluginLoader pluginLoader : partiallyInitialized) {
Plugin.removePlugin(pluginLoader.loadedFromUri);
}
partiallyInitialized.clear();
}
}
lazyInitialization = false;
}
/**
* Creates a new {@link URLClassLoader} and adds it to the list of classloaders
* we need to close if we close the corresponding plugin
*
* @param
* urls the URLs from which to load classes and resources
* @return a new {@link URLClassLoader}
*/
private URLClassLoader buildURLClassLoader(URL[] urls) {
URLClassLoader urlClassLoader = new URLClassLoader(urls);
createdClassLoaders.add(urlClassLoader);
return urlClassLoader;
}
/**
* Creates a new {@link URLClassLoader} and adds it to the list of classloaders
* we need to close if we close the corresponding plugin
*
* @param
* urls the URLs from which to load classes and resources
* @param
* parent the parent class loader for delegation
* @return a new {@link URLClassLoader}
*/
private URLClassLoader buildURLClassLoader(URL[] urls, ClassLoader parent) {
URLClassLoader urlClassLoader = new URLClassLoader(urls, parent);
createdClassLoaders.add(urlClassLoader);
return urlClassLoader;
}
/**
* Closes the class loaders created in this {@link PluginLoader}
* @throws IOException if a class loader fails to close, in that case the other classloaders won't be closed
*/
@Override
public void close() throws IOException {
for (URLClassLoader urlClassLoader : createdClassLoaders) {
urlClassLoader.close();
}
}
/**
* Patch for issue 3429143: allow plugins load classes/resources from 3rd
* party jars
*
* @param url
* plugin jar location as url
* @return non empty list with url to be used for loading classes from given
* plugin. If plugin jar contains standard Java manifest file, all
* entries of its "Class-Path" attribute will be translated to the
* jar-relative url's and added to the returned list. If plugin jar
* does not contains a manifest, or manifest does not have
* "Class-Path" attribute, the given argument will be the only entry
* in the array.
* @throws PluginException
*/
private static @Nonnull URL[] createClassloaderUrls(@Nonnull URL url) throws PluginException {
List urls = new ArrayList<>();
urls.add(url);
Manifest mf = null;
File f = new File(url.getPath());
// default: try with jar/zip/war etc files
if (!f.isDirectory()) {
JarInputStream jis = null;
try {
jis = new JarInputStream(url.openStream());
mf = jis.getManifest();
} catch (IOException ioe) {
throw new PluginException("Failed loading manifest for plugin jar: " + url, ioe);
} finally {
IO.close(jis);
}
} else {
// If this is not a jar/zip/war etc file, can we can load from directory?
// Allow plugins be loaded from "exploded jar" directories (e.g. while debugging
// 3rd party FB plugin projects in Eclipse without packaging them to jars at all)
File manifest = guessManifest(f);
if (manifest != null) {
FileInputStream is = null;
try {
is = new FileInputStream(manifest);
mf = new Manifest(is);
} catch (IOException e) {
throw new PluginException("Failed loading manifest for plugin jar: " + url, e);
} finally {
IO.close(is);
}
}
}
if (mf != null) {
try {
addClassPathFromManifest(url, urls, mf);
} catch (MalformedURLException e) {
throw new PluginException("Failed loading manifest for plugin jar: " + url, e);
}
}
return urls.toArray(new URL[0]);
}
private static void addClassPathFromManifest(@Nonnull URL url, @Nonnull List urls,
@Nonnull Manifest mf) throws MalformedURLException {
Attributes atts = mf.getMainAttributes();
if (atts == null) {
return;
}
String classPath = atts.getValue(Attributes.Name.CLASS_PATH);
if (classPath != null) {
String jarRoot = url.toString();
jarRoot = jarRoot.substring(0, jarRoot.lastIndexOf('/') + 1);
String[] jars = classPath.split(",");
for (String jar : jars) {
jar = jarRoot + jar.trim();
urls.add(new URL(jar));
}
}
}
/**
* Trying to find the manifest of "exploded plugin" in the current dir, "standard jar" manifest
* location or "standard" Eclipse location (sibling to the current classpath)
*/
@CheckForNull
private static File guessManifest(@Nonnull File parent) {
File file = new File(parent, "MANIFEST.MF");
if (!file.isFile()) {
file = new File(parent, "META-INF/MANIFEST.MF");
}
if (!file.isFile()) {
file = new File(parent, "../META-INF/MANIFEST.MF");
}
if (file.isFile()) {
return file;
}
return null;
}
/**
* 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() throws PluginException {
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);
plugin = init();
loadPluginComponents();
Plugin.putPlugin(null, plugin);
}
/**
* Fake plugin.
*/
@Deprecated
public PluginLoader(boolean fake, URL url) {
classLoader = getClass().getClassLoader();
classLoaderForResources = classLoader;
corePlugin = false;
initialPlugin = true;
optionalPlugin = false;
loadedFrom = url;
try {
loadedFromUri = loadedFrom.toURI();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Failed to parse uri: " + loadedFrom);
}
jarName = getJarName(loadedFrom);
plugin = null;
}
@Nonnull
private static URL computeCoreUrl() {
URL from;
String findBugsClassFile = ClassName.toSlashedClassName(FindBugs.class) + ".class";
URL me = FindBugs.class.getClassLoader().getResource(findBugsClassFile);
LOG.debug("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);
}
LOG.debug("Core class files loaded from {}", from);
return from;
}
public URL getURL() {
return loadedFrom;
}
public URI getURI() {
return loadedFromUri;
}
private static URI toUri(URL url) throws PluginException {
try {
return url.toURI();
} catch (URISyntaxException e) {
throw new PluginException("Bad uri: " + url, e);
}
}
private static String getJarName(URL url) {
String location = url.getPath();
int i = location.lastIndexOf('/');
location = location.substring(i + 1);
return location;
}
public ClassLoader getClassLoader() {
if (classLoader == null) {
throw new IllegalStateException("Plugin not completely initialized; classloader not set yet");
}
return classLoader;
}
/**
* Get the Plugin.
*
* @throws PluginException
* if the plugin cannot be fully loaded
*/
public Plugin loadPlugin() throws PluginException {
return getPlugin();
}
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);
LOG.debug("Trying to load {} from {}", name, url);
if (IO.verifyURL(url)) {
return url;
}
} catch (MalformedURLException e) {
assert true;
}
}
if (classLoaderForResources instanceof URLClassLoader) {
URLClassLoader urlClassLoader = (URLClassLoader) classLoaderForResources;
LOG.debug("Trying to load {} using URLClassLoader.findResource", name);
LOG.debug(" from urls: {}", Arrays.asList(urlClassLoader.getURLs()));
URL url = urlClassLoader.findResource(name);
if (url == null) {
url = urlClassLoader.findResource("/" + name);
}
if (IO.verifyURL(url)) {
return url;
}
}
LOG.debug("Trying to load {} using ClassLoader.getResource", name);
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 = resourceFromFindbugsJar(name);
if (u != null) {
return u;
}
u = PluginLoader.class.getResource(name);
if (u != null) {
return u;
}
u = PluginLoader.class.getResource("/" + name);
return u;
}
/**
* Try to load resource from JAR file of Findbugs core plugin.
* @param slashedResourceName Name of resource to load
* @return URL which points resource in jar file, or null if JAR file not found
*/
@CheckForNull
private static URL resourceFromFindbugsJar(String slashedResourceName) {
try {
@Nullable
URL findbugsJar = getFindbugsJar();
if (findbugsJar == null) {
return null;
}
assert findbugsJar.getProtocol().equals("file");
try (ZipFile jarFile = new ZipFile(new File(findbugsJar.toURI()))) {
ZipEntry entry = jarFile.getEntry(slashedResourceName);
if (entry != null) {
return resourceFromPlugin(findbugsJar, slashedResourceName);
}
} catch (ZipException e) {
LOG.warn("Failed to load resourceFromFindbugsJar: {} is not valid zip file.", findbugsJar, e);
} catch (IOException e) {
LOG.warn("Failed to load resourceFromFindbugsJar: IOException was thrown at zip file {} loading.",
findbugsJar, e);
}
} catch (MalformedURLException | URISyntaxException e) {
LOG.warn("Failed to load resourceFromFindbugsJar: Resource name is {}", slashedResourceName, e);
}
return null;
}
/**
* @return URL of findbugs.jar,
* or null if found no jar file which contains FindBugs.class
* @throws MalformedURLException
*/
@CheckForNull
private static URL getFindbugsJar() throws MalformedURLException {
String findBugsClassFile = ClassName.toSlashedClassName(FindBugs.class) + ".class";
URL me = FindBugs.class.getClassLoader().getResource(findBugsClassFile);
if (me == null) {
return null;
}
if (!"jar".equals(me.getProtocol())) {
return null;
}
String u = me.toString();
String jarPath = u.substring(4, u.indexOf("!/"));
return new URL(jarPath);
}
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
assert true;
}
}
}
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
assert true;
}
}
}
return null;
}
private static Class extends T> getClass(ClassLoader loader, @DottedClassName String className, Class 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 {
LOG.debug("Loading plugin from {}", loadedFrom);
// Plugin descriptor (a.k.a, "findbugs.xml"). Defines
// the bug detectors and bug patterns that the plugin provides.
Document pluginDescriptor = getPluginDescriptor();
List messageCollectionList = getMessageDocuments();
Plugin constructedPlugin = constructMinimalPlugin(pluginDescriptor, messageCollectionList);
// Success!
LOG.debug("Loaded {} from {}", constructedPlugin.getPluginId(), loadedFrom);
return constructedPlugin;
}
private void loadPluginComponents()
throws PluginException {
Document pluginDescriptor = getPluginDescriptor();
List messageCollectionList = getMessageDocuments();
// Create PluginComponents
try {
List componentNodeList = XMLUtil.selectNodes(pluginDescriptor, "/FindbugsPlugin/PluginComponent");
for (Node componentNode : componentNodeList) {
@DottedClassName
String componentKindname = componentNode.valueOf("@componentKind");
if (componentKindname == null) {
throw new PluginException("Missing @componentKind for " + plugin.getPluginId()
+ " loaded from " + loadedFrom);
}
@DottedClassName
String componentClassname = componentNode.valueOf("@componentClass");
if (componentClassname == null) {
throw new PluginException("Missing @componentClassname for " + plugin.getPluginId()
+ " loaded from " + loadedFrom);
}
String componentId = componentNode.valueOf("@id");
if (componentId == null) {
throw new PluginException("Missing @id for " + plugin.getPluginId()
+ " loaded from " + loadedFrom);
}
try {
String propertiesLocation = componentNode.valueOf("@properties");
boolean disabled = Boolean.valueOf(componentNode.valueOf("@disabled"));
Node filterMessageNode = findMessageNode(messageCollectionList,
"/MessageCollection/PluginComponent[@id='" + componentId + "']",
"Missing Cloud description for PluginComponent " + componentId);
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) {
AnalysisContext.logError("Could not load properties for " + plugin.getPluginId() + " component " + componentId
+ " from " + propertiesLocation);
continue;
}
properties.loadPropertiesFromURL(properiesURL);
}
List propertyNodes = XMLUtil.selectNodes(componentNode, "Property");
for (Node node : propertyNodes) {
String key = node.valueOf("@key");
String value = node.getText();
properties.setProperty(key, value);
}
Class> componentKind = classLoader.loadClass(componentKindname);
loadComponentPlugin(plugin, componentKind, componentClassname, componentId, disabled, description, details,
properties);
} catch (RuntimeException e) {
AnalysisContext.logError("Unable to load ComponentPlugin " + componentId +
" : " + componentClassname + " implementing " + componentKindname, e);
}
}
// Create FindBugsMains
if (!FindBugs.isNoMains()) {
List findBugsMainList = XMLUtil.selectNodes(pluginDescriptor, "/FindbugsPlugin/FindBugsMain");
for (Node main : findBugsMainList) {
String className = main.valueOf("@class");
if (className == null) {
throw new PluginException("Missing @class for FindBugsMain in plugin" + plugin.getPluginId()
+ " loaded from " + loadedFrom);
}
String cmd = main.valueOf("@cmd");
if (cmd == null) {
throw new PluginException("Missing @cmd for for FindBugsMain in plugin " + plugin.getPluginId()
+ " loaded from " + loadedFrom);
}
String kind = main.valueOf("@kind");
boolean analysis = Boolean.valueOf(main.valueOf("@analysis"));
Element mainMessageNode = (Element) findMessageNode(messageCollectionList,
"/MessageCollection/FindBugsMain[@cmd='" + cmd
// + " and @class='" + className
+ "']/Description",
"Missing FindBugsMain description for cmd " + cmd);
String description = mainMessageNode.getTextTrim();
try {
Class> mainClass = classLoader.loadClass(className);
plugin.addFindBugsMain(mainClass, cmd, description, kind, analysis);
} catch (Exception e) {
String msg = "Unable to load FindBugsMain " + cmd +
" : " + className + " in plugin " + plugin.getPluginId()
+ " loaded from " + loadedFrom;
PluginException e2 = new PluginException(msg, e);
AnalysisContext.logError(msg, e2);
}
}
}
List detectorNodeList = XMLUtil.selectNodes(pluginDescriptor, "/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.isNoAnalysis()) {
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, !"true".equals(disabled), 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("\n");
buf.append("Detector Description \n");
buf.append(detailHTML);
buf.append("\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
List elements = XMLUtil.selectNodes(orderingConstraintsNode, "./SplitPass|./WithinPass");
for (Element constraintElement : elements) {
// 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 ("SplitPass".equals(constraintElement.getName())) {
plugin.addInterPassOrderingConstraint(constraint);
} else {
plugin.addIntraPassOrderingConstraint(constraint);
}
}
}
// register global Category descriptions
List categoryNodeListGlobal = XMLUtil.selectNodes(pluginDescriptor, "/FindbugsPlugin/BugCategory");
for (Node categoryNode : categoryNodeListGlobal) {
String key = categoryNode.valueOf("@category");
if ("".equals(key)) {
throw new PluginException("BugCategory element with missing category attribute");
}
BugCategory bc = plugin.addOrCreateBugCategory(key);
boolean hidden = Boolean.valueOf(categoryNode.valueOf("@hidden"));
if (hidden) {
bc.setHidden(hidden);
}
}
for (Document messageCollection : messageCollectionList) {
List categoryNodeList = XMLUtil.selectNodes(messageCollection, "/MessageCollection/BugCategory");
LOG.debug("found {} categories in {}", categoryNodeList.size(), plugin.getPluginId());
for (Node categoryNode : categoryNodeList) {
String key = categoryNode.valueOf("@category");
if ("".equals(key)) {
throw new PluginException("BugCategory element with missing category attribute");
}
BugCategory bc = plugin.addOrCreateBugCategory(key);
String shortDesc = getChildText(categoryNode, "Description");
bc.setShortDescription(shortDesc);
try {
String abbrev = getChildText(categoryNode, "Abbreviation");
if (bc.getAbbrev() == null) {
bc.setAbbrev(abbrev);
LOG.debug("category {} abbrev -> {}", key, abbrev);
} else {
LOG.debug("rejected abbrev '{}' for category {}: {}", abbrev, key, bc.getAbbrev());
}
} catch (PluginException pe) {
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);
LOG.debug("category {} details -> {}", key, details);
} else {
LOG.debug("rejected details [{}] for category {}: [{}]", details, key, bc.getDetailText());
}
} catch (PluginException pe) {
// do nothing -- LongDescription is optional
}
}
}
// Create BugPatterns
List bugPatternNodeList = XMLUtil.selectNodes(pluginDescriptor, "/FindbugsPlugin/BugPattern");
for (Node bugPatternNode : bugPatternNodeList) {
String type = bugPatternNode.valueOf("@type");
String abbrev = bugPatternNode.valueOf("@abbrev");
String category = bugPatternNode.valueOf("@category");
boolean experimental = Boolean.parseBoolean(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);
Node bugsUrlNode = messageNode.getDocument().selectSingleNode("/MessageCollection/Plugin/" + (experimental ? "AllBugsUrl" : "BugsUrl"));
String bugsUrl = bugsUrlNode == null ? null : bugsUrlNode.getText();
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, experimental, shortDesc, longDesc, detailText, bugsUrl, 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 definedBugCodes = new HashSet<>();
for (Document messageCollection : messageCollectionList) {
List bugCodeNodeList = XMLUtil.selectNodes(messageCollection, "/MessageCollection/BugCode");
for (Node bugCodeNode : bugCodeNodeList) {
String abbrev = bugCodeNode.valueOf("@abbrev");
if ("".equals(abbrev)) {
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
.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
LOG.debug("No {} for plugin {}", BugRanker.FILENAME, plugin.getPluginId());
}
BugRanker ranker = new BugRanker(bugRankURL);
plugin.setBugRanker(ranker);
} catch (IOException e) {
throw new PluginException("Couldn't parse \"" + BugRanker.FILENAME + "\"", e);
}
}
private Plugin constructMinimalPlugin(Document pluginDescriptor, List messageCollectionList)
throws DuplicatePluginIdError {
// Get the unique plugin id (or generate one, if none is present)
// Unique plugin id
String pluginId = pluginDescriptor.valueOf(XPATH_PLUGIN_PLUGINID);
if ("".equals(pluginId)) {
synchronized (PluginLoader.class) {
pluginId = "plugin" + nextUnknownId++;
}
}
cannotDisable = Boolean.parseBoolean(pluginDescriptor.valueOf("/FindbugsPlugin/@cannotDisable"));
String de = pluginDescriptor.valueOf("/FindbugsPlugin/@defaultenabled");
if (de != null && "false".equals(de.toLowerCase().trim())) {
optionalPlugin = true;
}
if (optionalPlugin) {
cannotDisable = false;
}
if (!loadedPluginIds.add(pluginId)) {
Plugin existingPlugin = Plugin.getByPluginId(pluginId);
URL u = existingPlugin == null ? null : existingPlugin.getPluginLoader().getURL();
if (cannotDisable && initialPlugin) {
throw new DuplicatePluginIdError(pluginId, loadedFrom, u);
} else {
throw new DuplicatePluginIdException(pluginId, loadedFrom, u);
}
}
parentId = pluginDescriptor.valueOf("/FindbugsPlugin/@parentid");
String version = pluginDescriptor.valueOf("/FindbugsPlugin/@version");
String releaseDate = pluginDescriptor.valueOf("/FindbugsPlugin/@releaseDate");
// Create the Plugin object (but don't assign to the plugin field yet,
// since we're still not sure if everything will load correctly)
Date parsedDate = parseDate(releaseDate);
Plugin constructedPlugin = new Plugin(pluginId, version, parsedDate, this, !optionalPlugin, cannotDisable);
// Set provider and website, if specified
String provider = pluginDescriptor.valueOf(XPATH_PLUGIN_PROVIDER).trim();
if (!"".equals(provider)) {
constructedPlugin.setProvider(provider);
}
String website = pluginDescriptor.valueOf(XPATH_PLUGIN_WEBSITE).trim();
if (!"".equals(website)) {
try {
constructedPlugin.setWebsite(website);
} catch (URISyntaxException e1) {
AnalysisContext.logError("Plugin " + constructedPlugin.getPluginId() + " has invalid website: " + website, e1);
}
}
String updateUrl = pluginDescriptor.valueOf("/FindbugsPlugin/@update-url").trim();
if (!"".equals(updateUrl)) {
try {
constructedPlugin.setUpdateUrl(updateUrl);
} catch (URISyntaxException e1) {
AnalysisContext.logError("Plugin " + constructedPlugin.getPluginId() + " has invalid update check URL: " + website, e1);
}
}
// Set short description, if specified
Node pluginShortDesc = null;
try {
pluginShortDesc = findMessageNode(messageCollectionList, XPATH_PLUGIN_SHORT_DESCRIPTION,
"no plugin description");
} catch (PluginException e) {
// Missing description is not fatal, so ignore
}
if (pluginShortDesc != null) {
constructedPlugin.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) {
constructedPlugin.setDetailedDescription(detailedDescription.getText().trim());
}
List globalOptionNodes = XMLUtil.selectNodes(pluginDescriptor, "/FindbugsPlugin/GlobalOptions/Property");
for (Node optionNode : globalOptionNodes) {
String key = optionNode.valueOf("@key");
String value = optionNode.getText().trim();
constructedPlugin.setMyGlobalOption(key, value);
}
return constructedPlugin;
}
public Document getPluginDescriptor() throws PluginException, PluginDoesntContainMetadataException {
Document pluginDescriptor;
// 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 " + this);
}
LOG.debug("PluginLoader found {} at: {}", name, 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 = XMLUtil.buildSAXReader();
try (InputStream input = IO.openNonCachedStream(findbugsXML_URL);
Reader r = UTF8.bufferedReader(input)) {
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);
}
return pluginDescriptor;
}
private static List getPotentialMessageFiles() {
// Load the message collections
Locale locale = Locale.getDefault();
String language = locale.getLanguage();
String country = locale.getCountry();
List potential = new ArrayList<>(3);
if (country != null) {
potential.add("messages_" + language + "_" + country + ".xml");
}
potential.add("messages_" + language + ".xml");
potential.add("messages.xml");
return potential;
}
private List getMessageDocuments() throws PluginException {
// List of message translation files in decreasing order of precedence
ArrayList messageCollectionList = new ArrayList<>();
PluginException caught = null;
for (String m : getPotentialMessageFiles()) {
try {
addCollection(messageCollectionList, m);
} catch (PluginException e) {
caught = e;
AnalysisContext.logError(
"Error loading localized message file:" + m, e);
}
}
if (messageCollectionList.isEmpty()) {
if (caught != null) {
throw caught;
}
throw new PluginException("No message.xml files found");
}
return messageCollectionList;
}
private void loadComponentPlugin(Plugin plugin,
Class componentKind, @DottedClassName String componentClassname, String filterId,
boolean disabled, String description, String details, PropertyBundle properties) throws PluginException {
Class extends T> componentClass = null;
if (!FindBugs.isNoAnalysis() || componentKind == edu.umd.cs.findbugs.bugReporter.BugReporterDecorator.class) {
componentClass = getClass(classLoader, componentClassname, componentKind);
}
ComponentPlugin componentPlugin = new ComponentPlugin<>(plugin, filterId, classLoader, componentClass,
properties, !disabled, description, details);
plugin.addComponentPlugin(componentKind, componentPlugin);
}
private static Date parseDate(String releaseDate) {
if (releaseDate == null || releaseDate.length() == 0) {
return null;
}
try {
SimpleDateFormat releaseDateFormat = new SimpleDateFormat("MM/dd/yyyy hh:mm aa z", Locale.ENGLISH);
Date result = releaseDateFormat.parse(releaseDate);
return result;
} catch (ParseException e) {
AnalysisContext.logError("unable to parse date " + releaseDate, e);
return null;
}
}
private 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 (!"".equals(categoryName)) {
if ("reporting".equals(categoryName)) {
return new ReportingDetectorFactorySelector(spanPlugins ? null : plugin);
} else if ("training".equals(categoryName)) {
return new ByInterfaceDetectorFactorySelector(spanPlugins ? null : plugin, TrainingDetector.class);
} else if ("interprocedural".equals(categoryName)) {
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 (!"".equals(superName)) {
try {
Class> superClass = Class.forName(superName, true, classLoader);
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 messageCollectionList, String filename) throws PluginException {
URL messageURL = getResource(filename);
if (messageURL != null) {
SAXReader reader = XMLUtil.buildSAXReader();
try (InputStream input = IO.openNonCachedStream(messageURL);
Reader stream = UTF8.bufferedReader(input)) {
Document messageCollection;
messageCollection = reader.read(stream);
messageCollectionList.add(messageCollection);
} catch (IOException | DocumentException e) {
throw new PluginException("Couldn't parse \"" + messageURL + "\"", e);
}
}
}
private static Node findMessageNode(List 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 findMessageText(List messageCollectionList, String xpath, String missingMsg) {
for (Document document : messageCollectionList) {
Node node = document.selectSingleNode(xpath);
if (node != null) {
return node.getText().trim();
}
}
return 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();
}
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() {
lazyInitialization = true;
loadCorePlugin();
if (JavaWebStart.isRunningViaJavaWebstart()) {
installWebStartPlugins();
} else {
installStandardPlugins();
installUserInstalledPlugins();
}
Set> 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);
System.out.println("Loading " + e.getKey() + " from " + url);
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 {
SecurityManagerHandler.disableSecurityManager();
} catch (Throwable e) {
assert true; // keep going
}
}
finishLazyInitialization();
}
private static void loadCorePlugin() {
try {
Plugin plugin = Plugin.getPlugin(null);
if (plugin != null) {
throw new IllegalStateException("Already loaded");
}
PluginLoader pluginLoader = new PluginLoader();
plugin = pluginLoader.getPlugin();
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 {
getPluginLoader(u, PluginLoader.class.getClassLoader(), initial, optional);
} catch (DuplicatePluginIdException ignored) {
assert true;
} catch (PluginException e) {
AnalysisContext.logError("Unable to load plugin from " + u, e);
LOG.debug("Unable to load plugin from {}", u, e);
}
}
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 (IOException e) {
DetectorFactoryCollection.jawsDebugMessage("error loading " + url + " : " + e.getMessage());
}
}
} catch (IOException e) {
DetectorFactoryCollection.jawsDebugMessage("error : " + e.getMessage());
} finally {
Util.closeSilently(in);
}
}
}
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);
}
static public class Summary {
public final String id;
public final String description;
public final String provider;
public final String webbsite;
public Summary(String id, String description, String provider, String website) {
super();
this.id = id;
this.description = description;
this.provider = provider;
this.webbsite = website;
}
}
public static Summary validate(File file) throws IllegalArgumentException {
String path = file.getPath();
if (!file.getName().endsWith(".jar")) {
String message = "File " + path + " is not a .jar file";
throw new IllegalArgumentException(message);
}
if (!file.isFile() || !file.canRead()) {
String message = "File " + path
+ " is not a file or is not readable";
throw new IllegalArgumentException(message);
}
if (file.length() == 0) {
String message = "File " + path + " is empty";
throw new IllegalArgumentException(message);
}
try (ZipFile zip = new ZipFile(file)) {
ZipEntry findbugsXML = zip.getEntry("findbugs.xml");
if (findbugsXML == null) {
throw new IllegalArgumentException(
"plugin doesn't contain a findbugs.xml file");
}
ZipEntry messagesXML = zip.getEntry("messages.xml");
if (messagesXML == null) {
throw new IllegalArgumentException(
"plugin doesn't contain a messages.xml file");
}
Document pluginDocument = parseDocument(zip.getInputStream(findbugsXML));
String pluginId = pluginDocument.valueOf(XPATH_PLUGIN_PLUGINID).trim();
String provider = pluginDocument.valueOf(XPATH_PLUGIN_PROVIDER).trim();
String website = pluginDocument.valueOf(XPATH_PLUGIN_WEBSITE).trim();
List msgDocuments = new ArrayList<>(3);
for (String msgFile : getPotentialMessageFiles()) {
ZipEntry msgEntry = zip.getEntry(msgFile);
if (msgEntry == null) {
continue;
}
Document msgDocument = parseDocument(zip.getInputStream(msgEntry));
msgDocuments.add(msgDocument);
}
String shortDesc = findMessageText(msgDocuments,
XPATH_PLUGIN_SHORT_DESCRIPTION, "");
return new Summary(pluginId, shortDesc, provider, website);
} catch (DocumentException e) {
throw new IllegalArgumentException(e);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
private static Document parseDocument(@WillClose InputStream in) throws DocumentException {
Reader r = UTF8.bufferedReader(in);
try {
SAXReader reader = XMLUtil.buildSAXReader();
Document d = reader.read(r);
return d;
} finally {
Util.closeSilently(r);
}
}
}