View Javadoc

1   /***
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd;
5   
6   import net.sourceforge.pmd.util.ResourceLoader;
7   import org.w3c.dom.Document;
8   import org.w3c.dom.Element;
9   import org.w3c.dom.Node;
10  import org.w3c.dom.NodeList;
11  import org.xml.sax.SAXException;
12  
13  import javax.xml.parsers.DocumentBuilder;
14  import javax.xml.parsers.DocumentBuilderFactory;
15  import javax.xml.parsers.ParserConfigurationException;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.util.ArrayList;
19  import java.util.HashSet;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Properties;
23  import java.util.Set;
24  import java.util.StringTokenizer;
25  
26  // Note that ruleset parsing may fail on JDK 1.6 beta
27  // due to this bug - http://www.netbeans.org/issues/show_bug.cgi?id=63257
28  
29  public class RuleSetFactory {
30  
31      private static class OverrideParser {
32          private Element ruleElement;
33  
34          public OverrideParser(Element ruleElement) {
35              this.ruleElement = ruleElement;
36          }
37  
38          public void overrideAsNecessary(Rule rule) {
39              if (ruleElement.hasAttribute("name")) {
40                  rule.setName(ruleElement.getAttribute("name"));
41              }
42              if (ruleElement.hasAttribute("message")) {
43                  rule.setMessage(ruleElement.getAttribute("message"));
44              }
45              if (ruleElement.hasAttribute("externalInfoUrl")) {
46                  rule.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
47              }
48              for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
49                  Node node = ruleElement.getChildNodes().item(i);
50                  if (node.getNodeType() == Node.ELEMENT_NODE) {
51                      if (node.getNodeName().equals("description")) {
52                          rule.setDescription(parseTextNode(node));
53                      } else if (node.getNodeName().equals("example")) {
54                          rule.setExample(parseTextNode(node));
55                      } else if (node.getNodeName().equals("priority")) {
56                          rule.setPriority(Integer.parseInt(parseTextNode(node)));
57                      } else if (node.getNodeName().equals("properties")) {
58                          Properties p = new Properties();
59                          parsePropertiesNode(p, node);
60                          rule.addProperties(p);
61                      }
62                  }
63              }
64          }
65      }
66  
67      private ClassLoader classLoader;
68      private int minPriority = Rule.LOWEST_PRIORITY;
69  
70      public void setMinimumPriority(int minPriority) {
71          this.minPriority = minPriority;
72      }
73  
74      /***
75       * Returns an Iterator of RuleSet objects loaded from descriptions from the
76       * "rulesets.properties" resource.
77       *
78       * @return an iterator of RuleSet objects
79       */
80      public Iterator getRegisteredRuleSets() throws RuleSetNotFoundException {
81          try {
82              Properties props = new Properties();
83              props.load(ResourceLoader
84                      .loadResourceAsStream("rulesets/rulesets.properties"));
85              String rulesetFilenames = props.getProperty("rulesets.filenames");
86              List ruleSets = new ArrayList();
87              for (StringTokenizer st = new StringTokenizer(rulesetFilenames, ","); st
88                      .hasMoreTokens();) {
89                  ruleSets.add(createRuleSet(st.nextToken()));
90              }
91              return ruleSets.iterator();
92          } catch (IOException ioe) {
93              throw new RuntimeException("Couldn't find rulesets.properties; please ensure that the rulesets directory is on the classpath.  Here's the current classpath: "
94                      + System.getProperty("java.class.path"));
95          }
96      }
97  
98      /***
99       * Create a RuleSets from a list of names.
100      *
101      * @param ruleSetFileNames comma-separated list of rule set files.
102      * @param class            a RuleSets object
103      * @throws RuleSetNotFoundException
104      */
105     public RuleSets createRuleSets(String ruleSetFileNames, ClassLoader classLoader)
106             throws RuleSetNotFoundException {
107         RuleSets ruleSets = new RuleSets();
108 
109         for (StringTokenizer st = new StringTokenizer(ruleSetFileNames, ","); st
110                 .hasMoreTokens();) {
111             RuleSet ruleSet = createSingleRuleSet(st.nextToken().trim(), classLoader);
112             ruleSets.addRuleSet(ruleSet);
113         }
114 
115         return ruleSets;
116     }
117 
118     /***
119      * Create a RuleSets from a list of names, using the classloader of this class.
120      *
121      * @param ruleSetFileNames comma-separated list of rule set files.
122      * @throws RuleSetNotFoundException
123      */
124     public RuleSets createRuleSets(String ruleSetFileNames)
125             throws RuleSetNotFoundException {
126         return createRuleSets(ruleSetFileNames, getClass().getClassLoader());
127     }
128 
129     /***
130      * Create a ruleset from a name or from a list of names
131      *
132      * @param name        name of rule set file loaded as a resource
133      * @param classLoader the classloader used to load the ruleset and subsequent rules
134      * @return the new ruleset
135      * @throws RuleSetNotFoundException
136      * @deprecated Use createRuleSets instead, because this method puts all rules in one
137      *             single RuleSet object, and thus removes name and language of the
138      *             originating rule set files.
139      */
140     public RuleSet createRuleSet(String name, ClassLoader classLoader)
141             throws RuleSetNotFoundException {
142         RuleSets ruleSets = createRuleSets(name, classLoader);
143 
144         RuleSet result = new RuleSet();
145         RuleSet[] allRuleSets = ruleSets.getAllRuleSets();
146         for (int i = 0; i < allRuleSets.length; i++) {
147             result.addRuleSet(allRuleSets[i]);
148         }
149 
150         return result;
151     }
152 
153     /***
154      * Creates a ruleset. If passed a comma-delimited string
155      * (rulesets/basic.xml,rulesets/unusedcode.xml) it will parse that string and create a
156      * new ruleset for each item in the list. Same as createRuleSet(name,
157      * ruleSetFactory.getClassLoader()).
158      *
159      * @param name name of rule set file loaded as a resource
160      * @deprecated Use createRuleSets instead, because this method puts all rules in one
161      *             single RuleSet object, and thus removes name and language of the
162      *             originating rule set files.
163      */
164     public RuleSet createRuleSet(String name) throws RuleSetNotFoundException {
165         return createRuleSet(name, getClass().getClassLoader());
166     }
167 
168     /***
169      * Create a ruleset from a name
170      *
171      * @param name        name of rule set file loaded as a resource
172      * @param classLoader the classloader used to load the ruleset and subsequent rules
173      * @return the new ruleset
174      * @throws RuleSetNotFoundException
175      */
176     public RuleSet createSingleRuleSet(String ruleSetFileName, ClassLoader classLoader)
177             throws RuleSetNotFoundException {
178         return createRuleSet(tryToGetStreamTo(ruleSetFileName, classLoader), classLoader);
179     }
180 
181     /***
182      * Create a ruleset from a name
183      *
184      * @param name name of rule set file loaded as a resource
185      * @return the new ruleset
186      * @throws RuleSetNotFoundException
187      */
188     public RuleSet createSingleRuleSet(String ruleSetFileName)
189             throws RuleSetNotFoundException {
190         return createRuleSet(tryToGetStreamTo(ruleSetFileName, getClass()
191                 .getClassLoader()));
192     }
193 
194     /***
195      * Create a ruleset from an inputsteam. Same as createRuleSet(inputStream,
196      * ruleSetFactory.getClassLoader()).
197      *
198      * @param inputStream an input stream that contains a ruleset descripion
199      * @return a new ruleset
200      */
201     public RuleSet createRuleSet(InputStream inputStream) {
202         return createRuleSet(inputStream, getClass().getClassLoader());
203     }
204 
205     /***
206      * Create a ruleset from an input stream with a specified class loader
207      *
208      * @param inputStream an input stream that contains a ruleset descripion
209      * @param classLoader a class loader used to load rule classes
210      * @return a new ruleset
211      */
212     public RuleSet createRuleSet(InputStream inputStream, ClassLoader classLoader) {
213         try {
214             this.classLoader = classLoader;
215             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
216             Document doc = builder.parse(inputStream);
217             Element root = doc.getDocumentElement();
218 
219             RuleSet ruleSet = new RuleSet();
220             ruleSet.setName(root.getAttribute("name"));
221             ruleSet.setLanguage(Language.getByName(root.getAttribute("language")));
222 
223             NodeList nodeList = root.getChildNodes();
224             for (int i = 0; i < nodeList.getLength(); i++) {
225                 Node node = nodeList.item(i);
226                 if (node.getNodeType() == Node.ELEMENT_NODE) {
227                     if (node.getNodeName().equals("description")) {
228                         ruleSet.setDescription(parseTextNode(node));
229                     } else if (node.getNodeName().equals("rule")) {
230                         parseRuleNode(ruleSet, node);
231                     }
232                 }
233             }
234 
235             return ruleSet;
236         } catch (ClassNotFoundException cnfe) {
237             cnfe.printStackTrace();
238             throw new RuntimeException("Couldn't find that class " + cnfe.getMessage());
239         } catch (InstantiationException ie) {
240             ie.printStackTrace();
241             throw new RuntimeException("Couldn't find that class " + ie.getMessage());
242         } catch (IllegalAccessException iae) {
243             iae.printStackTrace();
244             throw new RuntimeException("Couldn't find that class " + iae.getMessage());
245         } catch (ParserConfigurationException pce) {
246             pce.printStackTrace();
247             throw new RuntimeException("Couldn't find that class " + pce.getMessage());
248         } catch (RuleSetNotFoundException rsnfe) {
249             rsnfe.printStackTrace();
250             throw new RuntimeException("Couldn't find that class " + rsnfe.getMessage());
251         } catch (IOException ioe) {
252             ioe.printStackTrace();
253             throw new RuntimeException("Couldn't find that class " + ioe.getMessage());
254         } catch (SAXException se) {
255             se.printStackTrace();
256             throw new RuntimeException("Couldn't find that class " + se.getMessage());
257         }
258     }
259 
260     /***
261      * Try to load a resource with the specified class loader
262      *
263      * @param name   a resource name (contains a ruleset description)
264      * @param loader a class loader used to load that rule set description
265      * @return an inputstream to that resource
266      * @throws RuleSetNotFoundException
267      */
268     private InputStream tryToGetStreamTo(String name, ClassLoader loader)
269             throws RuleSetNotFoundException {
270         InputStream in = ResourceLoader.loadResourceAsStream(name, loader);
271         if (in == null) {
272             throw new RuleSetNotFoundException("Can't find resource "
273                     + name
274                     + ".  Make sure the resource is a valid file or URL or is on the CLASSPATH.  Here's the current classpath: "
275                     + System.getProperty("java.class.path"));
276         }
277         return in;
278     }
279 
280     /***
281      * Parse a rule node
282      *
283      * @param ruleSet  the ruleset being constructed
284      * @param ruleNode must be a rule element node
285      */
286     private void parseRuleNode(RuleSet ruleSet, Node ruleNode)
287             throws ClassNotFoundException, InstantiationException,
288             IllegalAccessException, RuleSetNotFoundException {
289         Element ruleElement = (Element) ruleNode;
290         String ref = ruleElement.getAttribute("ref");
291         if (ref.trim().length() == 0) {
292             parseInternallyDefinedRuleNode(ruleSet, ruleNode);
293         } else {
294             parseExternallyDefinedRuleNode(ruleSet, ruleNode);
295         }
296     }
297 
298     /***
299      * Process a rule definition node
300      *
301      * @param ruleSet  the ruleset being constructed
302      * @param ruleNode must be a rule element node
303      */
304     private void parseInternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode)
305             throws ClassNotFoundException, InstantiationException, IllegalAccessException {
306         Element ruleElement = (Element) ruleNode;
307 
308         String attribute = ruleElement.getAttribute("class");
309         Rule rule = (Rule) classLoader.loadClass(attribute).newInstance();
310 
311         rule.setName(ruleElement.getAttribute("name"));
312         rule.setMessage(ruleElement.getAttribute("message"));
313         rule.setRuleSetName(ruleSet.getName());
314         rule.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
315 
316         if (ruleElement.hasAttribute("dfa")
317                 && ruleElement.getAttribute("dfa").equals("true")) {
318             rule.setUsesDFA();
319         }
320 
321         for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
322             Node node = ruleElement.getChildNodes().item(i);
323             if (node.getNodeType() == Node.ELEMENT_NODE) {
324                 if (node.getNodeName().equals("description")) {
325                     rule.setDescription(parseTextNode(node));
326                 } else if (node.getNodeName().equals("example")) {
327                     rule.setExample(parseTextNode(node));
328                 } else if (node.getNodeName().equals("priority")) {
329                     rule.setPriority(new Integer(parseTextNode(node).trim()).intValue());
330                 } else if (node.getNodeName().equals("properties")) {
331                     Properties p = new Properties();
332                     parsePropertiesNode(p, node);
333                     for (Iterator j = p.keySet().iterator(); j.hasNext();) {
334                         String key = (String) j.next();
335                         rule.addProperty(key, p.getProperty(key));
336                     }
337                 }
338             }
339         }
340         if (rule.getPriority() <= minPriority) {
341             ruleSet.addRule(rule);
342         }
343     }
344 
345     /***
346      * Process a reference to a rule
347      *
348      * @param ruleSet  the ruleset being constructucted
349      * @param ruleNode must be a rule element node
350      */
351     private void parseExternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode)
352             throws RuleSetNotFoundException {
353         Element ruleElement = (Element) ruleNode;
354         String ref = ruleElement.getAttribute("ref");
355         if (ref.endsWith("xml")) {
356             parseRuleNodeWithExclude(ruleSet, ruleElement, ref);
357         } else {
358             parseRuleNodeWithSimpleReference(ruleSet, ruleNode, ref);
359         }
360     }
361 
362     /***
363      * Parse a rule node with a simple reference
364      *
365      * @param ruleSet the ruleset being constructed
366      * @param ref     a reference to a rule
367      */
368     private void parseRuleNodeWithSimpleReference(RuleSet ruleSet, Node ruleNode,
369                                                   String ref) throws RuleSetNotFoundException {
370         RuleSetFactory rsf = new RuleSetFactory();
371 
372         ExternalRuleID externalRuleID = new ExternalRuleID(ref);
373         RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader
374                 .loadResourceAsStream(externalRuleID.getFilename()));
375         Rule externalRule = externalRuleSet.getRuleByName(externalRuleID.getRuleName());
376         if (externalRule == null) {
377             throw new IllegalArgumentException("Unable to find rule "
378                     + externalRuleID.getRuleName()
379                     + "; perhaps the rule name is mispelled?");
380         }
381 
382         OverrideParser p = new OverrideParser((Element) ruleNode);
383         p.overrideAsNecessary(externalRule);
384 
385         if (externalRule.getPriority() <= minPriority) {
386             ruleSet.addRule(externalRule);
387         }
388     }
389 
390     /***
391      * Parse a reference rule node with excludes
392      *
393      * @param ruleSet     the ruleset being constructed
394      * @param ruleElement must be a rule element
395      * @param ref         the ruleset reference
396      */
397     private void parseRuleNodeWithExclude(RuleSet ruleSet, Element ruleElement, String ref)
398             throws RuleSetNotFoundException {
399         NodeList excludeNodes = ruleElement.getChildNodes();
400         Set excludes = new HashSet();
401         for (int i = 0; i < excludeNodes.getLength(); i++) {
402             if ((excludeNodes.item(i).getNodeType() == Node.ELEMENT_NODE)
403                     && (excludeNodes.item(i).getNodeName().equals("exclude"))) {
404                 Element excludeElement = (Element) excludeNodes.item(i);
405                 excludes.add(excludeElement.getAttribute("name"));
406             }
407         }
408 
409         RuleSetFactory rsf = new RuleSetFactory();
410         RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader
411                 .loadResourceAsStream(ref));
412         for (Iterator i = externalRuleSet.getRules().iterator(); i.hasNext();) {
413             Rule rule = (Rule) i.next();
414             if (!excludes.contains(rule.getName()) && rule.getPriority() <= minPriority) {
415                 ruleSet.addRule(rule);
416             }
417         }
418     }
419 
420     private static String parseTextNode(Node exampleNode) {
421         StringBuffer buffer = new StringBuffer();
422         for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) {
423             Node node = exampleNode.getChildNodes().item(i);
424             if (node.getNodeType() == Node.CDATA_SECTION_NODE
425                     || node.getNodeType() == Node.TEXT_NODE) {
426                 buffer.append(node.getNodeValue());
427             }
428         }
429         return buffer.toString();
430     }
431 
432     /***
433      * Parse a properties node
434      *
435      * @param propertiesNode must be a properties element node
436      */
437     private static void parsePropertiesNode(Properties p, Node propertiesNode) {
438         for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) {
439             Node node = propertiesNode.getChildNodes().item(i);
440             if (node.getNodeType() == Node.ELEMENT_NODE
441                     && node.getNodeName().equals("property")) {
442                 parsePropertyNode(p, node);
443             }
444         }
445     }
446 
447     /***
448      * Parse a property node
449      *
450      * @param propertyNode must be a property element node
451      */
452     private static void parsePropertyNode(Properties p, Node propertyNode) {
453         Element propertyElement = (Element) propertyNode;
454         String name = propertyElement.getAttribute("name");
455         String value = propertyElement.getAttribute("value");
456         // TODO String desc = propertyElement.getAttribute("description");
457         if (value.trim().length() == 0) {
458             for (int i = 0; i < propertyNode.getChildNodes().getLength(); i++) {
459                 Node node = propertyNode.getChildNodes().item(i);
460                 if ((node.getNodeType() == Node.ELEMENT_NODE)
461                         && node.getNodeName().equals("value")) {
462                     value = parseTextNode(node);
463                 }
464             }
465         }
466         if (propertyElement.hasAttribute("pluginname")) {
467             p.setProperty("pluginname", propertyElement.getAttributeNode("pluginname")
468                     .getNodeValue());
469         }
470         p.setProperty(name, value);
471     }
472 }