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