/*
 * 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.ZipFile;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.WillClose;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

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.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.updates.UpdateChecker;
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 Also:
/** * 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 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 boolean DEBUG = SystemProperties.getBoolean("findbugs.debug.PluginLoader"); static boolean lazyInitialization = false; static LinkedList<PluginLoader> partiallyInitialized = new LinkedList<PluginLoader>(); // 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;
plugin Id for parent plugin
/** plugin Id for parent plugin */
String parentId; static HashSet<String> loadedPluginIds = new HashSet<String>(); static { if (DEBUG) { System.out.println("Debugging plugin loading. FindBugs version " + Version.getReleaseWithDateIfDev()); } loadInitialPlugins(); }
Constructor.
Params:
  • url – the URL of the plugin Jar file
Throws:
/** * 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.
Params:
  • url – the URL of the plugin Jar file
  • parent – the parent classloader
Deprecated:Use PluginLoader(URL, URI, ClassLoader, boolean, boolean) instead
/** * 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.
Params:
  • url – the URL of the plugin Jar file
  • uri –
  • parent – the parent classloader
  • isInitial – is this plugin loaded from one of the standard locations for plugins
  • optional – is this an optional plugin
/** * 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 = new URLClassLoader(loaderURLs); loadedFrom = url; loadedFromUri = uri; jarName = getJarName(url); corePlugin = false; initialPlugin = isInitial; optionalPlugin = optional; plugin = init(); if (!hasParent()) { classLoader = new URLClassLoader(loaderURLs, parent); } else { if (parent != PluginLoader.class.getClassLoader()) { throw new IllegalArgumentException("Can't specify parentid " + parentId + " and provide a seperate class loader"); } Plugin parentPlugin = Plugin.getByPluginId(parentId); if (parentPlugin != null) { parent = parentPlugin.getClassLoader(); classLoader = new URLClassLoader(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<String> unresolved = new LinkedList<String>(); Set<String> needed = new TreeSet<String>(); for (Iterator<PluginLoader> 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 = new URLClassLoader(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 (Iterator<PluginLoader> i = partiallyInitialized.iterator(); i.hasNext();) { Plugin.removePlugin(i.next().loadedFromUri); } partiallyInitialized.clear(); } } lazyInitialization = false; }
Patch for issue 3429143: allow plugins load classes/resources from 3rd party jars
Params:
  • url – plugin jar location as url
Throws:
Returns: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.
/** * 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<URL> urls = new ArrayList<URL>(); 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[urls.size()]); } private static void addClassPathFromManifest(@Nonnull URL url, @Nonnull List<URL> 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)
/** * 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 –
/** * 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.
/** * Fake plugin. */
@Deprecated public PluginLoader(boolean fake, URL url) throws PluginException { 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; } private static 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; } 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
/** * 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.
Params:
  • name – resource to get
Returns:URL for the resource, or null if it could not be found
/** * 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 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 <T> Class<? extends T> getClass(ClassLoader loader, @DottedClassName 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 = getPluginDescriptor(); List<Document> messageCollectionList = getMessageDocuments(); Plugin constructedPlugin = constructMinimalPlugin(pluginDescriptor, messageCollectionList); // Success! if (DEBUG) { System.out.println("Loaded " + constructedPlugin.getPluginId() + " from " + loadedFrom); } return constructedPlugin; } private void loadPluginComponents() throws PluginException { Document pluginDescriptor = getPluginDescriptor(); List<Document> messageCollectionList = getMessageDocuments(); List<Node> cloudNodeList = XMLUtil.selectNodes(pluginDescriptor, "/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 = XMLUtil.selectNodes(cloudNode, "Property"); for (Node node : propertyNodes) { String key = node.valueOf("@key"); String value = node.getText().trim(); properties.setProperty(key, value); } CloudPlugin cloudPlugin = new CloudPluginBuilder().setFindbugsPluginId(plugin.getPluginId()).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> 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<Node> 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<Node> 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<Node> 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("<!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 List<Element> 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<Node> 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<Node> categoryNodeList = XMLUtil.selectNodes(messageCollection, "/MessageCollection/BugCategory"); if (DEBUG) { System.out.println("found " + categoryNodeList.size() + " categories in " + 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); 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 } } } // Create BugPatterns List<Node> 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<String> definedBugCodes = new HashSet<String>(); for (Document messageCollection : messageCollectionList) { List<Node> 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 .<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 " + 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<Document> 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"); if ((releaseDate == null || releaseDate.length() == 0) && isCorePlugin()) { releaseDate = Version.CORE_PLUGIN_RELEASE_DATE; } // 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<Node> 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); } 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(); Reader r = null; try { 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); } finally { IO.close(r); } return pluginDescriptor; } private static List<String> getPotentialMessageFiles() { // Load the message collections Locale locale = Locale.getDefault(); String language = locale.getLanguage(); String country = locale.getCountry(); List<String> potential = new ArrayList<String>(3); if (country != null) { potential.add("messages_" + language + "_" + country + ".xml"); } potential.add("messages_" + language + ".xml"); potential.add("messages.xml"); return potential; } private List<Document> getMessageDocuments() throws PluginException { // List of message translation files in decreasing order of precedence ArrayList<Document> messageCollectionList = new ArrayList<Document>(); 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 <T> void loadComponentPlugin(Plugin plugin, Class<T> 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<T> componentPlugin = new ComponentPlugin<T>(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(UpdateChecker.PLUGIN_RELEASE_DATE_FMT, Locale.ENGLISH); Date result = releaseDateFormat.parse(releaseDate); return result; } catch (ParseException e) { AnalysisContext.logError("unable to parse date " + releaseDate, e); return null; } } 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 (!"".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); 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 (IOException | DocumentException e) { throw new PluginException("Couldn't parse \"" + messageURL + "\"", e); } } } 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 findMessageText(List<Document> 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<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); 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 { System.setSecurityManager(null); } 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); if (DEBUG) { e.printStackTrace(); } } } 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); } ZipFile zip = null; try { 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<Document> msgDocuments = new ArrayList<Document>(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); } finally { Util.closeSilently(zip); } } private static Document parseDocument(@WillClose InputStream in) throws DocumentException { Reader r = UTF8.bufferedReader(in); try { SAXReader reader = new SAXReader(); Document d = reader.read(r); return d; } finally { Util.closeSilently(r); } } }