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  
12  import javax.xml.parsers.DocumentBuilder;
13  import javax.xml.parsers.DocumentBuilderFactory;
14  import java.io.IOException;
15  import java.io.InputStream;
16  import java.util.ArrayList;
17  import java.util.HashSet;
18  import java.util.Iterator;
19  import java.util.List;
20  import java.util.Properties;
21  import java.util.Set;
22  import java.util.StringTokenizer;
23  
24  public class RuleSetFactory {
25      private ClassLoader classLoader;
26  
27      /***
28       * Returns an Iterator of RuleSet objects loaded from descriptions from
29       * the "rulesets.properties" resource or from the "rulesets.filenames" property.
30       * @return an iterator on RuleSet objects
31       */
32      public Iterator getRegisteredRuleSets() throws RuleSetNotFoundException {
33          try {
34              Properties props = new Properties();
35              props.load(ResourceLoader.loadResourceAsStream("rulesets/rulesets.properties"));
36              String rulesetFilenames = props.getProperty("rulesets.filenames");
37              List ruleSets = new ArrayList();
38              for (StringTokenizer st = new StringTokenizer(rulesetFilenames, ","); st.hasMoreTokens();) {
39                  ruleSets.add(createRuleSet(st.nextToken()));
40              }
41              return ruleSets.iterator();
42          } catch (IOException ioe) {
43              throw new RuntimeException("Couldn't find rulesets.properties; please ensure that the rulesets directory is on the classpath.  Here's the current classpath: " + System.getProperty("java.class.path"));
44          }
45      }
46  
47      /***
48       * Create a ruleset from a name or from a list of name
49       * @param name name of rule set file loaded as a resource
50       * @param classLoader the classloader used to load the ruleset and subsequent rules
51       * @return the new ruleset
52       * @throws RuleSetNotFoundException
53       */
54      public RuleSet createRuleSet(String name, ClassLoader classLoader) throws RuleSetNotFoundException {
55          RuleSet ruleSet = null;
56          setClassLoader(classLoader);
57          
58          if (name.indexOf(',') == -1) {
59              ruleSet = createRuleSet(tryToGetStreamTo(name, classLoader));
60          } else {
61              ruleSet = new RuleSet();
62              for (StringTokenizer st = new StringTokenizer(name, ","); st.hasMoreTokens();) {
63                  String ruleSetName = st.nextToken().trim();
64                  RuleSet tmpRuleSet = createRuleSet(ruleSetName, classLoader);
65                  ruleSet.addRuleSet(tmpRuleSet);
66              }
67          }
68  
69          return ruleSet;
70      }
71  
72      /***
73       * Creates a ruleset.  If passed a comma-delimited string (rulesets/basic.xml,rulesets/unusedcode.xml)
74       * it will parse that string and create a new ruleset for each item in the list.
75       * Same as createRuleSet(name, ruleSetFactory.getClassLoader()).
76       */
77      public RuleSet createRuleSet(String name) throws RuleSetNotFoundException {
78          return createRuleSet(name, getClass().getClassLoader());
79      }
80  
81      /***
82       * Create a ruleset from an inputsteam.
83       * Same as createRuleSet(inputStream, ruleSetFactory.getClassLoader()).
84       * @param inputStream an input stream  that contains a ruleset descripion
85       * @return a new ruleset
86       */
87      public RuleSet createRuleSet(InputStream inputStream) {
88          return createRuleSet(inputStream, getClass().getClassLoader());
89      }
90  
91      /***
92       * Create a ruleset from an input stream with a specified class loader
93       * @param inputStream an input stream that contains a ruleset descripion
94       * @param classLoader a class loader used to load rule classes
95       * @return a new ruleset
96       */
97      public RuleSet createRuleSet(InputStream inputStream, ClassLoader classLoader) {
98          try {
99              setClassLoader(classLoader);
100             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
101             Document doc = builder.parse(inputStream);
102             Element root = doc.getDocumentElement();
103 
104             RuleSet ruleSet = new RuleSet();
105             ruleSet.setName(root.getAttribute("name"));
106 
107             NodeList nodeList = root.getChildNodes();
108             for (int i = 0; i < nodeList.getLength(); i++) {
109                 Node node = nodeList.item(i);
110                 if (node.getNodeType() == Node.ELEMENT_NODE) {
111                     if (node.getNodeName().equals("description")) {
112                         parseDescriptionNode(ruleSet, node);
113                     } else if (node.getNodeName().equals("rule")) {
114                         parseRuleNode(ruleSet, node);
115                     }
116                 }
117             }
118 
119             return ruleSet;
120         } catch (Exception e) {
121             e.printStackTrace();
122             throw new RuntimeException("Couldn't read from that source: " + e.getMessage());
123         }
124     }
125 
126     /***
127      * Return the class loader used to load ruleset resources and rules
128      * @return
129      */
130     public ClassLoader getClassLoader() {
131         return classLoader;
132     }
133 
134     /***
135      * Sets the class loader used to load ruleset resources and rules 
136      * @param loader a class loader
137      */
138     public void setClassLoader(ClassLoader loader) {
139         classLoader = loader;
140     }
141 
142     /***
143      * Try to load a resource with the specified class loader
144      * @param name a resource name (contains a ruleset description)
145      * @param loader a class loader used to load that rule set description
146      * @return an inputstream to that resource
147      * @throws RuleSetNotFoundException
148      */
149     private InputStream tryToGetStreamTo(String name, ClassLoader loader) throws RuleSetNotFoundException {
150         InputStream in = ResourceLoader.loadResourceAsStream(name, loader);
151         if (in == null) {
152             throw new RuleSetNotFoundException("Can't find resource " + name + ".  Make sure the resource is a valid file or URL or is on the CLASSPATH");
153         }
154         
155         return in;
156     }
157     
158     /***
159      * Parse a ruleset description node
160      * @param ruleSet the ruleset being constructed
161      * @param descriptionNode must be a description element node
162      */
163     private void parseDescriptionNode(RuleSet ruleSet, Node descriptionNode) {
164         NodeList nodeList = descriptionNode.getChildNodes();
165         StringBuffer buffer = new StringBuffer();
166         for (int i = 0 ; i < nodeList.getLength(); i++) {
167             Node node = nodeList.item(i);
168             if (node.getNodeType() == Node.TEXT_NODE) {
169                 buffer.append(node.getNodeValue());
170             } else if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
171                 buffer.append(node.getNodeValue());
172             }
173         }
174         ruleSet.setDescription(buffer.toString());
175     }
176     
177     /***
178      * Parse a rule node
179      * @param ruleSet the ruleset being constructed
180      * @param ruleElement must be a rule element node
181      */
182     private void parseRuleNode(RuleSet ruleSet, Node ruleNode) throws ClassNotFoundException, InstantiationException, IllegalAccessException, RuleSetNotFoundException {
183         Element ruleElement = (Element) ruleNode;
184         String ref = ruleElement.getAttribute("ref");
185         if (ref.trim().length() == 0) {
186             parseInternallyDefinedRuleNode(ruleSet, ruleNode);
187         } else {
188             parseExternallyDefinedRuleNode(ruleSet, ruleNode);
189         }
190     }
191     
192     /***
193      * Process a rule definition node
194      * @param ruleSet the ruleset being constructed
195      * @param ruleNode must be a rule element node
196      */
197     private void parseInternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
198         Element ruleElement = (Element) ruleNode;
199 
200         String className = ruleElement.getAttribute("class");
201         String name = ruleElement.getAttribute("name");
202         String message = ruleElement.getAttribute("message");
203         Rule rule = (Rule) getClassLoader().loadClass(className).newInstance();
204         rule.setName(name);
205         rule.setMessage(message);
206         
207         NodeList nodeList = ruleElement.getChildNodes();
208         for (int i = 0; i < nodeList.getLength(); i++) {
209             Node node = nodeList.item(i);
210             if (node.getNodeType() == Node.ELEMENT_NODE) {
211                 if (node.getNodeName().equals("description")) {
212                     parseDescriptionNode(rule, node);
213                 } else if (node.getNodeName().equals("example")) {
214                     parseExampleNode(rule, node);
215                 } else if (node.getNodeName().equals("priority")) {
216                     parsePriorityNode(rule, node);
217                 } else if (node.getNodeName().equals("properties")) {
218                     parsePropertiesNode(rule, node);
219                 }
220             }
221         }
222         
223         ruleSet.addRule(rule);
224     }
225     
226     /***
227      * Process a reference to a rule
228      * @param ruleSet the ruleset being constructucted
229      * @param ruleNode must be a ruke element node
230      */
231     private void parseExternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode) throws RuleSetNotFoundException {
232         Element ruleElement = (Element) ruleNode;
233         String ref = ruleElement.getAttribute("ref");
234         if (ref.endsWith("xml")) {
235             parseRuleNodeWithExclude(ruleSet, ruleElement, ref);
236         } else {
237             parseRuleNodeWithSimpleReference(ruleSet, ref);
238         }
239     }
240     
241     /***
242      * Parse a rule node with a simple reference
243      * @param ruleSet the ruleset being constructed
244      * @param ref a reference to a rule
245      */
246     private void parseRuleNodeWithSimpleReference(RuleSet ruleSet, String ref) throws RuleSetNotFoundException {
247         RuleSetFactory rsf = new RuleSetFactory();
248         ExternalRuleID externalRuleID = new ExternalRuleID(ref);
249         RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader.loadResourceAsStream(externalRuleID.getFilename()));
250         ruleSet.addRule(externalRuleSet.getRuleByName(externalRuleID.getRuleName()));
251     }
252 
253     /***
254      * Parse a reference rule node with excludes
255      * @param ruleSet the ruleset being constructed
256      * @param ruleElement must be a rule element
257      * @param ref the ruleset reference
258      */    
259     private void parseRuleNodeWithExclude(RuleSet ruleSet, Element ruleElement, String ref) throws RuleSetNotFoundException {
260         NodeList excludeNodes = ruleElement.getChildNodes();
261         Set excludes = new HashSet();
262         for (int i=0; i<excludeNodes.getLength(); i++) {
263             Node node = excludeNodes.item(i);
264             if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("exclude"))) {
265                 Element excludeElement = (Element) node;
266                 excludes.add(excludeElement.getAttribute("name"));
267             }
268         }
269         
270         RuleSetFactory rsf = new RuleSetFactory();
271         RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader.loadResourceAsStream(ref));
272         for (Iterator i = externalRuleSet.getRules().iterator(); i.hasNext();) {
273             Rule rule = (Rule) i.next();
274             if (!excludes.contains(rule.getName())) {
275                  ruleSet.addRule(rule);
276             }
277         }
278     }
279 
280     /***
281      * Process a rule descrtiprion node
282      * @param rule the rule being constructed
283      * @param descriptionNode must be a description element node
284      */
285     private void parseDescriptionNode(Rule rule, Node descriptionNode) {
286         NodeList nodeList = descriptionNode.getChildNodes();
287         StringBuffer buffer = new StringBuffer();
288         for (int i = 0; i < nodeList.getLength(); i++) {
289             Node node = nodeList.item(i);
290             if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
291                 buffer.append(node.getNodeValue());
292             } else if (node.getNodeType() == Node.TEXT_NODE) {
293                 buffer.append(node.getNodeValue());
294             }
295         }
296         rule.setDescription(buffer.toString());
297     }
298 
299     /***
300      * Process a rule example node
301      * @param rule the rule being constructed
302      * @param exampleNode must be a example element node
303      */
304     private void parseExampleNode(Rule rule, Node exampleNode) {
305         NodeList nodeList = exampleNode.getChildNodes();
306         StringBuffer buffer = new StringBuffer();
307         for (int i = 0; i < nodeList.getLength(); i++) {
308             Node node = nodeList.item(i);
309             if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
310                 buffer.append(node.getNodeValue());
311             } else if (node.getNodeType() == Node.TEXT_NODE) {
312                 buffer.append(node.getNodeValue());
313             }
314         }
315         rule.setExample(buffer.toString());
316     }
317     
318     /***
319      * Parse a priority node
320      * @param rule the rule being constructed
321      * @param priorityNode must be a priority element
322      */
323     private void parsePriorityNode(Rule rule, Node priorityNode) {
324         StringBuffer buffer = new StringBuffer();
325         NodeList nodeList = priorityNode.getChildNodes();
326         for (int i = 0; i < nodeList.getLength(); i++) {
327             Node node = nodeList.item(i);
328             if (node.getNodeType() == Node.TEXT_NODE) {
329                 buffer.append(node.getNodeValue());
330             }
331         }
332         rule.setPriority(new Integer(buffer.toString().trim()).intValue());
333     }
334     
335     /***
336      * Parse a properties node
337      * @param rule the rule being constructed
338      * @param propertiesNode must be a properties element node
339      */
340     private void parsePropertiesNode(Rule rule, Node propertiesNode) {
341         NodeList nodeList = propertiesNode.getChildNodes();
342         for (int i = 0; i < nodeList.getLength(); i++) {
343             Node node = nodeList.item(i);
344             if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("property"))) {
345                 parsePropertyNode(rule, node);
346             }
347         }
348     }
349     
350     /***
351      * Parse a property node
352      * @param rule the rule being constructed
353      * @param propertyNode must be a property element node
354      */
355     private void parsePropertyNode(Rule rule, Node propertyNode) {
356         Element propertyElement = (Element) propertyNode;
357         String name = propertyElement.getAttribute("name");
358         String value = propertyElement.getAttribute("value");
359         if (value.trim().length() == 0) {
360             NodeList nodeList = propertyNode.getChildNodes();
361             for (int i = 0; i < nodeList.getLength(); i++) {
362                 Node node = nodeList.item(i);
363                 if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("value"))) {
364                     value = parseValueNode(node);
365                 }
366             }
367         }
368         if (propertyElement.hasAttribute("pluginname")) {
369             rule.addProperty("pluginname", propertyElement.getAttributeNode("pluginname").getNodeValue());
370         }
371         rule.addProperty(name, value);
372     }
373     
374     /***
375      * Parse a value node
376      * @param valueNode must be a value element node
377      * @return the value
378      */
379     private String parseValueNode(Node valueNode) {
380         StringBuffer buffer = new StringBuffer();
381         NodeList nodeList = valueNode.getChildNodes();
382         for (int i = 0; i < nodeList.getLength(); i++) {
383             Node node = nodeList.item(i);
384             if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
385                 buffer.append(node.getNodeValue());
386             } else if (node.getNodeType() == Node.TEXT_NODE) {
387                 buffer.append(node.getNodeValue());
388             }
389         }
390         return buffer.toString();
391     }
392 }