KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > PluginLoader


1 /*
2  * FindBugs - Find bugs in Java programs
3  * Copyright (C) 2003-2005 University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs;
21
22 import java.net.URL JavaDoc;
23 import java.net.URLClassLoader JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.HashSet JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Locale JavaDoc;
28 import java.util.Set JavaDoc;
29
30 import org.dom4j.Document;
31 import org.dom4j.DocumentException;
32 import org.dom4j.Element;
33 import org.dom4j.Node;
34 import org.dom4j.io.SAXReader;
35
36 import edu.umd.cs.findbugs.plan.ByInterfaceDetectorFactorySelector;
37 import edu.umd.cs.findbugs.plan.DetectorFactorySelector;
38 import edu.umd.cs.findbugs.plan.DetectorOrderingConstraint;
39 import edu.umd.cs.findbugs.plan.ReportingDetectorFactorySelector;
40 import edu.umd.cs.findbugs.plan.SingleDetectorFactorySelector;
41
42 /**
43  * Loader for a FindBugs plugin.
44  * A plugin is a jar file containing two metadata files,
45  * "findbugs.xml" and "messages.xml". Those files specify
46  * <ul>
47  * <li> the bug pattern Detector classes,
48  * <li> the bug patterns detected (including all text for displaying
49  * detected instances of those patterns), and
50  * <li> the "bug codes" which group together related bug instances
51  * </ul>
52  *
53  * <p> The PluginLoader creates a Plugin object to store
54  * the Detector factories and metadata.</p>
55  *
56  * @author David Hovemeyer
57  * @see Plugin
58  * @see PluginException
59  */

60 public class PluginLoader extends URLClassLoader JavaDoc {
61
62     private static final boolean DEBUG = SystemProperties.getBoolean("findbugs.debug.PluginLoader");
63
64     // Keep a count of how many plugins we've seen without a
65
// "pluginid" attribute, so we can assign them all unique ids.
66
private static int nextUnknownId;
67
68     // The loaded Plugin
69
private Plugin plugin;
70
71     /**
72      * Constructor.
73      *
74      * @param url the URL of the plugin Jar file
75      * @throws PluginException if the plugin cannot be fully loaded
76      */

77     public PluginLoader(URL JavaDoc url) throws PluginException {
78         super(new URL JavaDoc[]{url});
79         init();
80     }
81
82     /**
83      * Constructor.
84      *
85      * @param url the URL of the plugin Jar file
86      * @param parent the parent classloader
87      */

88     public PluginLoader(URL JavaDoc url, ClassLoader JavaDoc parent) throws PluginException {
89         super(new URL JavaDoc[]{url}, parent);
90         init();
91     }
92
93     /**
94      * Get the Plugin.
95      * @throws PluginException if the plugin cannot be fully loaded
96      */

97     public Plugin getPlugin() throws PluginException {
98         if (plugin == null)
99             init();
100         return plugin;
101     }
102     
103     private void init() throws PluginException {
104         // Plugin descriptor (a.k.a, "findbugs.xml"). Defines
105
// the bug detectors and bug patterns that the plugin provides.
106
Document pluginDescriptor;
107         
108         // Unique plugin id
109
String JavaDoc pluginId;
110
111         // List of message translation files in decreasing order of precedence
112
ArrayList JavaDoc<Document> messageCollectionList = new ArrayList JavaDoc<Document>();
113
114         // Read the plugin descriptor
115
try {
116             URL JavaDoc descriptorURL = findResource("findbugs.xml");
117             if (descriptorURL == null)
118                 throw new PluginException("Couldn't find \"findbugs.xml\" in plugin");
119
120             SAXReader reader = new SAXReader();
121             pluginDescriptor = reader.read(descriptorURL);
122         } catch (DocumentException e) {
123             throw new PluginException("Couldn't parse \"findbugs.xml\"", e);
124         }
125         
126         // Get the unique plugin id (or generate one, if none is present)
127
pluginId = pluginDescriptor.valueOf("/FindbugsPlugin/@pluginid");
128         if (pluginId.equals("")) {
129             synchronized (PluginLoader.class) {
130                 pluginId = "plugin" + nextUnknownId++;
131             }
132         }
133
134         // See if the plugin is enabled or disabled by default.
135
// Note that if there is no "defaultenabled" attribute,
136
// then we assume that the plugin IS enabled by default.
137
String JavaDoc defaultEnabled = pluginDescriptor.valueOf("/FindbugsPlugin/@defaultenabled");
138         boolean pluginEnabled = defaultEnabled.equals("") || Boolean.valueOf(defaultEnabled).booleanValue();
139
140         // Load the message collections
141
try {
142             //Locale locale = Locale.getDefault();
143
Locale JavaDoc locale = I18N.defaultLocale;
144             String JavaDoc language = locale.getLanguage();
145             String JavaDoc country = locale.getCountry();
146
147             if (country != null)
148                 addCollection(messageCollectionList, "messages_" + language + "_" + country + ".xml");
149             addCollection(messageCollectionList, "messages_" + language + ".xml");
150             addCollection(messageCollectionList, "messages.xml");
151         } catch (DocumentException e) {
152             e.printStackTrace();
153             throw new PluginException("Couldn't parse \"messages.xml\"", e);
154         }
155         
156         // Create the Plugin object (but don't assign to the plugin field yet,
157
// since we're still not sure if everything will load correctly)
158
Plugin plugin = new Plugin(pluginId);
159         plugin.setEnabled(pluginEnabled);
160
161         // Set provider and website, if specified
162
String JavaDoc provider = pluginDescriptor.valueOf("/FindbugsPlugin/@provider");
163         if (!provider.equals(""))
164             plugin.setProvider(provider);
165         String JavaDoc website = pluginDescriptor.valueOf("/FindbugsPlugin/@website");
166         if (!website.equals(""))
167             plugin.setWebsite(website);
168
169         // Set short description, if specified
170
Node pluginShortDesc = null;
171         try {
172             pluginShortDesc = findMessageNode(
173                     messageCollectionList,
174                     "/MessageCollection/Plugin/ShortDescription",
175                     "no plugin description");
176         } catch (PluginException e) {
177             // Missing description is not fatal, so ignore
178
}
179         if (pluginShortDesc != null) {
180             plugin.setShortDescription(pluginShortDesc.getText());
181         }
182
183         // Create a DetectorFactory for all Detector nodes
184
try {
185             List JavaDoc<Node> detectorNodeList = pluginDescriptor.selectNodes("/FindbugsPlugin/Detector");
186             int detectorCount = 0;
187             for (Node detectorNode : detectorNodeList) {
188                 String JavaDoc className = detectorNode.valueOf("@class");
189                 String JavaDoc speed = detectorNode.valueOf("@speed");
190                 String JavaDoc disabled = detectorNode.valueOf("@disabled");
191                 String JavaDoc reports = detectorNode.valueOf("@reports");
192                 String JavaDoc requireJRE = detectorNode.valueOf("@requirejre");
193                 String JavaDoc hidden = detectorNode.valueOf("@hidden");
194
195                 //System.out.println("Found detector: class="+className+", disabled="+disabled);
196

197                 // Create DetectorFactory for the detector
198
Class JavaDoc<?> detectorClass = loadClass(className);
199                 if (!Detector.class.isAssignableFrom(detectorClass)
200                         && !Detector2.class.isAssignableFrom(detectorClass))
201                     throw new PluginException("Class " + className + " does not implement Detector or Detector2");
202                 DetectorFactory factory = new DetectorFactory(
203                         plugin,
204                         detectorClass, !disabled.equals("true"),
205                         speed, reports, requireJRE);
206                 if (Boolean.valueOf(hidden).booleanValue())
207                     factory.setHidden(true);
208                 factory.setPositionSpecifiedInPluginDescriptor(detectorCount++);
209                 plugin.addDetectorFactory(factory);
210
211                 // Find Detector node in one of the messages files,
212
// to get the detail HTML.
213
Node node = findMessageNode(messageCollectionList,
214                         "/MessageCollection/Detector[@class='" + className + "']/Details",
215                         "Missing Detector description for detector " + className);
216
217                 Element details = (Element) node;
218                 String JavaDoc detailHTML = details.getText();
219                 StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
220                 buf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n");
221                 buf.append("<HTML><HEAD><TITLE>Detector Description</TITLE></HEAD><BODY>\n");
222                 buf.append(detailHTML);
223                 buf.append("</BODY></HTML>\n");
224                 factory.setDetailHTML(buf.toString());
225             }
226         } catch (ClassNotFoundException JavaDoc e) {
227             throw new PluginException("Could not instantiate detector class: " + e, e);
228         }
229
230         // Create ordering constraints
231
Node orderingConstraintsNode =
232             pluginDescriptor.selectSingleNode("/FindbugsPlugin/OrderingConstraints");
233         if (orderingConstraintsNode != null) {
234             // Get inter-pass and intra-pass constraints
235
for (Element constraintElement : (List JavaDoc<Element>) orderingConstraintsNode.selectNodes("./SplitPass|./WithinPass"))
236             {
237                 // Create the selectors which determine which detectors are
238
// involved in the constraint
239
DetectorFactorySelector earlierSelector = getConstraintSelector(
240                         constraintElement, plugin, "Earlier", "EarlierCategory");
241                 DetectorFactorySelector laterSelector = getConstraintSelector(
242                         constraintElement, plugin, "Later", "LaterCategory");
243
244                 // Create the constraint
245
DetectorOrderingConstraint constraint = new DetectorOrderingConstraint(
246                         earlierSelector, laterSelector);
247
248                 // Add the constraint to the plugin
249
if (constraintElement.getName().equals("SplitPass"))
250                     plugin.addInterPassOrderingConstraint(constraint);
251                 else
252                     plugin.addIntraPassOrderingConstraint(constraint);
253             }
254         }
255
256         // register global Category descriptions
257
I18N i18n = I18N.instance();
258         for (Document messageCollection : messageCollectionList) {
259             List JavaDoc<Node> categoryNodeList = messageCollection.selectNodes("/MessageCollection/BugCategory");
260             if (DEBUG) System.out.println("found "+categoryNodeList.size()+" categories in "+messageCollection.getName());
261             for (Node categoryNode : categoryNodeList) {
262                 String JavaDoc key = categoryNode.valueOf("@category");
263                 if (key.equals(""))
264                     throw new PluginException("BugCategory element with missing category attribute");
265                 String JavaDoc shortDesc = getChildText(categoryNode, "Description");
266                 BugCategory bc = new BugCategory(key, shortDesc);
267                 boolean b = i18n.registerBugCategory(key, bc);
268                 if (DEBUG) System.out.println(b
269                     ? "category "+key+" -> "+shortDesc
270                     : "rejected \""+shortDesc+"\" for category "+key+": "+i18n.getBugCategoryDescription(key));
271                 /* Now set the abbreviation and details. Be prepared for messages_fr.xml
272                  * to specify only the shortDesc (though it should set the abbreviation
273                  * too) and fall back to messages.xml for the abbreviation and details. */

274                 if (!b) bc = i18n.getBugCategory(key); // get existing BugCategory object
275
try {
276                     String JavaDoc abbrev = getChildText(categoryNode, "Abbreviation");
277                     if (bc.getAbbrev() == null) {
278                         bc.setAbbrev(abbrev);
279                         if (DEBUG) System.out.println("category "+key+" abbrev -> "+abbrev);
280                     }
281                     else if (DEBUG) System.out.println("rejected abbrev '"+abbrev+"' for category "+key+": "+bc.getAbbrev());
282                 } catch (PluginException pe) {
283                     if (DEBUG) System.out.println("missing Abbreviation for category "+key+"/"+shortDesc);
284                     // do nothing else -- Abbreviation is required, but handle its omission gracefully
285
}
286                 try {
287                     String JavaDoc details = getChildText(categoryNode, "Details");
288                     if (bc.getDetailText() == null) {
289                         bc.setDetailText(details);
290                         if (DEBUG) System.out.println("category "+key+" details -> "+details);
291                     }
292                     else if (DEBUG) System.out.println("rejected details ["+details+"] for category "+key+": ["+bc.getDetailText()+']');
293                 } catch (PluginException pe) {
294                     // do nothing -- LongDescription is optional
295
}
296             }
297         }
298
299         // Create BugPatterns
300
List JavaDoc<Node> bugPatternNodeList = pluginDescriptor.selectNodes("/FindbugsPlugin/BugPattern");
301         for (Node bugPatternNode : bugPatternNodeList) {
302             String JavaDoc type = bugPatternNode.valueOf("@type");
303             String JavaDoc abbrev = bugPatternNode.valueOf("@abbrev");
304             String JavaDoc category = bugPatternNode.valueOf("@category");
305             String JavaDoc experimental = bugPatternNode.valueOf("@experimental");
306
307             // Find the matching element in messages.xml (or translations)
308
String JavaDoc query = "/MessageCollection/BugPattern[@type='" + type + "']";
309             Node messageNode = findMessageNode(messageCollectionList, query,
310                     "messages.xml missing BugPattern element for type " + type);
311
312             String JavaDoc shortDesc = getChildText(messageNode, "ShortDescription");
313             String JavaDoc longDesc = getChildText(messageNode, "LongDescription");
314             String JavaDoc detailText = getChildText(messageNode, "Details");
315
316             BugPattern bugPattern = new BugPattern(type, abbrev, category,
317                     Boolean.valueOf(experimental).booleanValue(),
318                     shortDesc, longDesc, detailText);
319             plugin.addBugPattern(bugPattern);
320             boolean unknownCategory = (null == i18n.getBugCategory(category));
321             if (unknownCategory) {
322                 i18n.registerBugCategory(category, new BugCategory(category, category));
323                 // no desc, but at least now it will appear in I18N.getBugCategories().
324
if (DEBUG) System.out.println("Category "+category+" (of BugPattern "
325                     +type+") has no description in messages*.xml");
326                 //TODO report this even if !DEBUG
327
}
328         }
329
330         // Create BugCodes
331
Set JavaDoc<String JavaDoc> definedBugCodes = new HashSet JavaDoc<String JavaDoc>();
332         for (Document messageCollection : messageCollectionList) {
333             List JavaDoc<Node> bugCodeNodeList = messageCollection.selectNodes("/MessageCollection/BugCode");
334             for (Node bugCodeNode : bugCodeNodeList) {
335                 String JavaDoc abbrev = bugCodeNode.valueOf("@abbrev");
336                 if (abbrev.equals(""))
337                     throw new PluginException("BugCode element with missing abbrev attribute");
338                 if (definedBugCodes.contains(abbrev))
339                     continue;
340                 String JavaDoc description = bugCodeNode.getText();
341                 BugCode bugCode = new BugCode(abbrev, description);
342                 plugin.addBugCode(bugCode);
343                 definedBugCodes.add(abbrev);
344             }
345
346         }
347         
348         // Success!
349
// Assign to the plugin field, so getPlugin() can return the
350
// new Plugin object.
351
this.plugin = plugin;
352
353     }
354
355     private static DetectorFactorySelector getConstraintSelector(
356             Element constraintElement,
357             Plugin plugin,
358             String JavaDoc singleDetectorElementName,
359             String JavaDoc detectorCategoryElementName) throws PluginException {
360         Node node = constraintElement.selectSingleNode("./" + singleDetectorElementName);
361         if (node != null) {
362             String JavaDoc detectorClass = node.valueOf("@class");
363             return new SingleDetectorFactorySelector(plugin, detectorClass);
364         }
365         
366         node = constraintElement.selectSingleNode("./" + detectorCategoryElementName);
367         if (node != null) {
368             String JavaDoc categoryName = node.valueOf("@name");
369             boolean spanPlugins = Boolean.valueOf(node.valueOf("@spanplugins")).booleanValue();
370             if (categoryName.equals("reporting")) {
371                 return new ReportingDetectorFactorySelector(spanPlugins ? null : plugin);
372             } else if (categoryName.equals("training")) {
373                 return new ByInterfaceDetectorFactorySelector(spanPlugins ? null : plugin, TrainingDetector.class);
374             } else if (categoryName.equals("interprocedural")) {
375                 return new ByInterfaceDetectorFactorySelector(spanPlugins ? null : plugin, InterproceduralFirstPassDetector.class);
376             } else {
377                 throw new PluginException("Invalid constraint selector node");
378             }
379         }
380         
381         throw new PluginException("Invalid constraint selector node");
382     }
383
384     private String JavaDoc lookupDetectorClass(Plugin plugin, String JavaDoc name) throws PluginException {
385         // If the detector name contains '.' characters, assume it is
386
// fully qualified already. Otherwise, assume it is a short
387
// name that resolves to another detector in the same plugin.
388

389         if (name.indexOf('.') < 0) {
390             DetectorFactory factory = plugin.getFactoryByShortName(name);
391             if (factory == null)
392                 throw new PluginException("No detector found for short name '" + name + "'");
393             name = factory.getFullName();
394         }
395         return name;
396     }
397
398     private void addCollection(List JavaDoc<Document> messageCollectionList, String JavaDoc filename)
399             throws DocumentException {
400         URL JavaDoc messageURL = findResource(filename);
401         if (messageURL != null) {
402             SAXReader reader = new SAXReader();
403             Document messageCollection = reader.read(messageURL);
404             messageCollectionList.add(messageCollection);
405         }
406     }
407
408     private static Node findMessageNode(List JavaDoc<Document> messageCollectionList, String JavaDoc xpath,
409                                         String JavaDoc missingMsg) throws PluginException {
410
411         for (Document document : messageCollectionList) {
412             Node node = document.selectSingleNode(xpath);
413             if (node != null)
414                 return node;
415         }
416         throw new PluginException(missingMsg);
417     }
418
419     private static String JavaDoc getChildText(Node node, String JavaDoc childName) throws PluginException {
420         Node child = node.selectSingleNode(childName);
421         if (child == null)
422             throw new PluginException("Could not find child \"" + childName + "\" for node");
423         return child.getText();
424     }
425
426 }
427
428 // vim:ts=4
429
Popular Tags