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