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
27
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
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 }