1
2
3
4 package net.sourceforge.pmd;
5
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.Iterator;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Properties;
14 import java.util.logging.Logger;
15
16 import javax.xml.parsers.DocumentBuilder;
17 import javax.xml.parsers.DocumentBuilderFactory;
18 import javax.xml.parsers.ParserConfigurationException;
19
20 import net.sourceforge.pmd.lang.Language;
21 import net.sourceforge.pmd.lang.LanguageVersion;
22 import net.sourceforge.pmd.lang.rule.MockRule;
23 import net.sourceforge.pmd.lang.rule.RuleReference;
24 import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorWrapper;
25 import net.sourceforge.pmd.lang.rule.properties.factories.PropertyDescriptorUtil;
26 import net.sourceforge.pmd.util.ResourceLoader;
27 import net.sourceforge.pmd.util.StringUtil;
28
29 import org.w3c.dom.Document;
30 import org.w3c.dom.Element;
31 import org.w3c.dom.Node;
32 import org.w3c.dom.NodeList;
33 import org.xml.sax.SAXException;
34
35
36
37
38
39
40 public class RuleSetFactory {
41
42 private static final Logger LOG = Logger.getLogger(RuleSetFactory.class.getName());
43
44 private ClassLoader classLoader = RuleSetFactory.class.getClassLoader();
45 private RulePriority minimumPriority = RulePriority.LOW;
46 private boolean warnDeprecated = false;
47
48
49
50
51
52
53 public void setClassLoader(ClassLoader classLoader) {
54 this.classLoader = classLoader;
55 }
56
57
58
59
60
61
62
63 public void setMinimumPriority(RulePriority minimumPriority) {
64 this.minimumPriority = minimumPriority;
65 }
66
67
68
69
70
71 public void setWarnDeprecated(boolean warnDeprecated) {
72 this.warnDeprecated = warnDeprecated;
73 }
74
75
76
77
78
79
80
81 public Iterator<RuleSet> getRegisteredRuleSets() throws RuleSetNotFoundException {
82 String rulesetsProperties = null;
83 try {
84 List<RuleSetReferenceId> ruleSetReferenceIds = new ArrayList<RuleSetReferenceId>();
85 for (Language language : Language.findWithRuleSupport()) {
86 Properties props = new Properties();
87 rulesetsProperties = "rulesets/" + language.getTerseName() + "/rulesets.properties";
88 props.load(ResourceLoader.loadResourceAsStream(rulesetsProperties));
89 String rulesetFilenames = props.getProperty("rulesets.filenames");
90 ruleSetReferenceIds.addAll(RuleSetReferenceId.parse(rulesetFilenames));
91 }
92 return createRuleSets(ruleSetReferenceIds).getRuleSetsIterator();
93 } catch (IOException ioe) {
94 throw new RuntimeException("Couldn't find " + rulesetsProperties
95 + "; please ensure that the rulesets directory is on the classpath. The current classpath is: "
96 + System.getProperty("java.class.path"));
97 }
98 }
99
100
101
102
103
104
105
106
107
108
109
110 public synchronized RuleSets createRuleSets(String referenceString) throws RuleSetNotFoundException {
111 return createRuleSets(RuleSetReferenceId.parse(referenceString));
112 }
113
114
115
116
117
118
119
120
121
122 public synchronized RuleSets createRuleSets(List<RuleSetReferenceId> ruleSetReferenceIds)
123 throws RuleSetNotFoundException {
124 RuleSets ruleSets = new RuleSets();
125 for (RuleSetReferenceId ruleSetReferenceId : ruleSetReferenceIds) {
126 RuleSet ruleSet = createRuleSet(ruleSetReferenceId);
127 ruleSets.addRuleSet(ruleSet);
128 }
129 return ruleSets;
130 }
131
132
133
134
135
136
137
138
139
140
141
142 public synchronized RuleSet createRuleSet(String referenceString) throws RuleSetNotFoundException {
143 List<RuleSetReferenceId> references = RuleSetReferenceId.parse(referenceString);
144 if (references.isEmpty()) {
145 throw new RuleSetNotFoundException("No RuleSetReferenceId can be parsed from the string: <"
146 + referenceString + ">");
147 }
148 return createRuleSet(references.get(0));
149 }
150
151
152
153
154
155
156
157
158
159
160 public synchronized RuleSet createRuleSet(RuleSetReferenceId ruleSetReferenceId) throws RuleSetNotFoundException {
161 return parseRuleSetNode(ruleSetReferenceId, ruleSetReferenceId.getInputStream(this.classLoader));
162 }
163
164
165
166
167
168
169
170
171
172
173
174 private Rule createRule(RuleSetReferenceId ruleSetReferenceId) throws RuleSetNotFoundException {
175 if (ruleSetReferenceId.isAllRules()) {
176 throw new IllegalArgumentException("Cannot parse a single Rule from an all Rule RuleSet reference: <"
177 + ruleSetReferenceId + ">.");
178 }
179 RuleSet ruleSet = createRuleSet(ruleSetReferenceId);
180 return ruleSet.getRuleByName(ruleSetReferenceId.getRuleName());
181 }
182
183
184
185
186
187
188
189
190 private RuleSet parseRuleSetNode(RuleSetReferenceId ruleSetReferenceId, InputStream inputStream) {
191 if (!ruleSetReferenceId.isExternal()) {
192 throw new IllegalArgumentException("Cannot parse a RuleSet from a non-external reference: <"
193 + ruleSetReferenceId + ">.");
194 }
195 try {
196 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
197 Document document = builder.parse(inputStream);
198 Element ruleSetElement = document.getDocumentElement();
199
200 RuleSet ruleSet = new RuleSet();
201 ruleSet.setFileName(ruleSetReferenceId.getRuleSetFileName());
202 ruleSet.setName(ruleSetElement.getAttribute("name"));
203
204 NodeList nodeList = ruleSetElement.getChildNodes();
205 for (int i = 0; i < nodeList.getLength(); i++) {
206 Node node = nodeList.item(i);
207 if (node.getNodeType() == Node.ELEMENT_NODE) {
208 String nodeName = node.getNodeName();
209 if ("description".equals(nodeName)) {
210 ruleSet.setDescription(parseTextNode(node));
211 } else if ("include-pattern".equals(nodeName)) {
212 ruleSet.addIncludePattern(parseTextNode(node));
213 } else if ("exclude-pattern".equals(nodeName)) {
214 ruleSet.addExcludePattern(parseTextNode(node));
215 } else if ("rule".equals(nodeName)) {
216 parseRuleNode(ruleSetReferenceId, ruleSet, node);
217 } else {
218 throw new IllegalArgumentException("Unexpected element <" + node.getNodeName()
219 + "> encountered as child of <ruleset> element.");
220 }
221 }
222 }
223
224 return ruleSet;
225 } catch (ClassNotFoundException cnfe) {
226 return classNotFoundProblem(cnfe);
227 } catch (InstantiationException ie) {
228 return classNotFoundProblem(ie);
229 } catch (IllegalAccessException iae) {
230 return classNotFoundProblem(iae);
231 } catch (ParserConfigurationException pce) {
232 return classNotFoundProblem(pce);
233 } catch (RuleSetNotFoundException rsnfe) {
234 return classNotFoundProblem(rsnfe);
235 } catch (IOException ioe) {
236 return classNotFoundProblem(ioe);
237 } catch (SAXException se) {
238 return classNotFoundProblem(se);
239 }
240 }
241
242 private static RuleSet classNotFoundProblem(Exception ex) throws RuntimeException {
243 ex.printStackTrace();
244 throw new RuntimeException("Couldn't find the class " + ex.getMessage());
245 }
246
247
248
249
250
251
252
253
254 private void parseRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode)
255 throws ClassNotFoundException, InstantiationException, IllegalAccessException, RuleSetNotFoundException {
256 Element ruleElement = (Element) ruleNode;
257 String ref = ruleElement.getAttribute("ref");
258 if (ref.endsWith("xml")) {
259 parseRuleSetReferenceNode(ruleSetReferenceId, ruleSet, ruleElement, ref);
260 } else if (StringUtil.isEmpty(ref)) {
261 parseSingleRuleNode(ruleSetReferenceId, ruleSet, ruleNode);
262 } else {
263 parseRuleReferenceNode(ruleSetReferenceId, ruleSet, ruleNode, ref);
264 }
265 }
266
267
268
269
270
271
272
273
274
275
276
277
278 private void parseRuleSetReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Element ruleElement,
279 String ref) throws RuleSetNotFoundException {
280 RuleSetReference ruleSetReference = new RuleSetReference();
281 ruleSetReference.setAllRules(true);
282 ruleSetReference.setRuleSetFileName(ref);
283 NodeList excludeNodes = ruleElement.getChildNodes();
284 for (int i = 0; i < excludeNodes.getLength(); i++) {
285 if (isElementNode(excludeNodes.item(i),"exclude")) {
286 Element excludeElement = (Element) excludeNodes.item(i);
287 ruleSetReference.addExclude(excludeElement.getAttribute("name"));
288 }
289 }
290
291 RuleSetFactory ruleSetFactory = new RuleSetFactory();
292 ruleSetFactory.setClassLoader(classLoader);
293 RuleSet otherRuleSet = ruleSetFactory.createRuleSet(RuleSetReferenceId.parse(ref).get(0));
294 for (Rule rule : otherRuleSet.getRules()) {
295 if (!ruleSetReference.getExcludes().contains(rule.getName())
296 && rule.getPriority().compareTo(minimumPriority) <= 0 && !rule.isDeprecated()) {
297 RuleReference ruleReference = new RuleReference();
298 ruleReference.setRuleSetReference(ruleSetReference);
299 ruleReference.setRule(rule);
300 ruleSet.addRule(ruleReference);
301 }
302 }
303 }
304
305
306
307
308
309
310
311
312
313 private void parseSingleRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode)
314 throws ClassNotFoundException, InstantiationException, IllegalAccessException {
315 Element ruleElement = (Element) ruleNode;
316
317
318 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName())
319 && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) {
320 return;
321 }
322
323 String attribute = ruleElement.getAttribute("class");
324 Class<?> c = classLoader.loadClass(attribute);
325 Rule rule = (Rule) c.newInstance();
326
327 rule.setName(ruleElement.getAttribute("name"));
328
329 if (ruleElement.hasAttribute("language")) {
330 String languageName = ruleElement.getAttribute("language");
331 Language language = Language.findByTerseName(languageName);
332 if (language == null) {
333 throw new IllegalArgumentException("Unknown Language '" + languageName + "' for Rule " + rule.getName()
334 + ", supported Languages are "
335 + Language.commaSeparatedTerseNames(Language.findWithRuleSupport()));
336 }
337 rule.setLanguage(language);
338 }
339
340 Language language = rule.getLanguage();
341 if (language == null) {
342 throw new IllegalArgumentException("Rule " + rule.getName()
343 + " does not have a Language; missing 'language' attribute?");
344 }
345
346 if (ruleElement.hasAttribute("minimumLanguageVersion")) {
347 String minimumLanguageVersionName = ruleElement.getAttribute("minimumLanguageVersion");
348 LanguageVersion minimumLanguageVersion = language.getVersion(minimumLanguageVersionName);
349 if (minimumLanguageVersion == null) {
350 throw new IllegalArgumentException("Unknown minimum Language Version '" + minimumLanguageVersionName
351 + "' for Language '" + language.getTerseName() + "' for Rule " + rule.getName()
352 + "; supported Language Versions are: "
353 + LanguageVersion.commaSeparatedTerseNames(language.getVersions()));
354 }
355 rule.setMinimumLanguageVersion(minimumLanguageVersion);
356 }
357
358 if (ruleElement.hasAttribute("maximumLanguageVersion")) {
359 String maximumLanguageVersionName = ruleElement.getAttribute("maximumLanguageVersion");
360 LanguageVersion maximumLanguageVersion = language.getVersion(maximumLanguageVersionName);
361 if (maximumLanguageVersion == null) {
362 throw new IllegalArgumentException("Unknown maximum Language Version '" + maximumLanguageVersionName
363 + "' for Language '" + language.getTerseName() + "' for Rule " + rule.getName()
364 + "; supported Language Versions are: "
365 + LanguageVersion.commaSeparatedTerseNames(language.getVersions()));
366 }
367 rule.setMaximumLanguageVersion(maximumLanguageVersion);
368 }
369
370 if (rule.getMinimumLanguageVersion() != null && rule.getMaximumLanguageVersion() != null) {
371 throw new IllegalArgumentException("The minimum Language Version '"
372 + rule.getMinimumLanguageVersion().getTerseName()
373 + "' must be prior to the maximum Language Version '"
374 + rule.getMaximumLanguageVersion().getTerseName() + "' for Rule " + rule.getName()
375 + "; perhaps swap them around?");
376 }
377
378 String since = ruleElement.getAttribute("since");
379 if (StringUtil.isNotEmpty(since)) {
380 rule.setSince(since);
381 }
382 rule.setMessage(ruleElement.getAttribute("message"));
383 rule.setRuleSetName(ruleSet.getName());
384 rule.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
385
386 if (hasAttributeSetTrue(ruleElement,"dfa")) {
387 rule.setUsesDFA();
388 }
389
390 if (hasAttributeSetTrue(ruleElement,"typeResolution")) {
391 rule.setUsesTypeResolution();
392 }
393
394 final NodeList nodeList = ruleElement.getChildNodes();
395 for (int i = 0; i < nodeList.getLength(); i++) {
396 Node node = nodeList.item(i);
397 if (node.getNodeType() != Node.ELEMENT_NODE) { continue; }
398 String nodeName = node.getNodeName();
399 if (nodeName.equals("description")) {
400 rule.setDescription(parseTextNode(node));
401 } else if (nodeName.equals("example")) {
402 rule.addExample(parseTextNode(node));
403 } else if (nodeName.equals("priority")) {
404 rule.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node).trim())));
405 } else if (nodeName.equals("properties")) {
406 parsePropertiesNode(rule, node);
407 } else {
408 throw new IllegalArgumentException("Unexpected element <" + nodeName
409 + "> encountered as child of <rule> element for Rule " + rule.getName());
410 }
411
412 }
413 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName()) || rule.getPriority().compareTo(minimumPriority) <= 0) {
414 ruleSet.addRule(rule);
415 }
416 }
417
418 private static boolean hasAttributeSetTrue(Element element, String attributeId) {
419 return element.hasAttribute(attributeId) && "true".equalsIgnoreCase(element.getAttribute(attributeId));
420 }
421
422
423
424
425
426
427
428
429
430
431
432 private void parseRuleReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode, String ref) throws RuleSetNotFoundException {
433 Element ruleElement = (Element) ruleNode;
434
435
436 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName())
437 && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) {
438 return;
439 }
440
441 RuleSetFactory ruleSetFactory = new RuleSetFactory();
442 ruleSetFactory.setClassLoader(classLoader);
443
444 RuleSetReferenceId otherRuleSetReferenceId = RuleSetReferenceId.parse(ref).get(0);
445 if (!otherRuleSetReferenceId.isExternal()) {
446 otherRuleSetReferenceId = new RuleSetReferenceId(ref, ruleSetReferenceId);
447 }
448 Rule referencedRule = ruleSetFactory.createRule(otherRuleSetReferenceId);
449 if (referencedRule == null) {
450 throw new IllegalArgumentException("Unable to find referenced rule "
451 + otherRuleSetReferenceId.getRuleName() + "; perhaps the rule name is mispelled?");
452 }
453
454 if (warnDeprecated && referencedRule.isDeprecated()) {
455 if (referencedRule instanceof RuleReference) {
456 RuleReference ruleReference = (RuleReference) referencedRule;
457 LOG.warning("Use Rule name " + ruleReference.getRuleSetReference().getRuleSetFileName() + "/"
458 + ruleReference.getName() + " instead of the deprecated Rule name " + otherRuleSetReferenceId
459 + ". Future versions of PMD will remove support for this deprecated Rule name usage.");
460 } else if (referencedRule instanceof MockRule) {
461 LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId
462 + " as it has been removed from PMD and no longer functions."
463 + " Future versions of PMD will remove support for this Rule.");
464 } else {
465 LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId
466 + " as it is scheduled for removal from PMD."
467 + " Future versions of PMD will remove support for this Rule.");
468 }
469 }
470
471 RuleSetReference ruleSetReference = new RuleSetReference();
472 ruleSetReference.setAllRules(false);
473 ruleSetReference.setRuleSetFileName(otherRuleSetReferenceId.getRuleSetFileName());
474
475 RuleReference ruleReference = new RuleReference();
476 ruleReference.setRuleSetReference(ruleSetReference);
477 ruleReference.setRule(referencedRule);
478
479 if (ruleElement.hasAttribute("deprecated")) {
480 ruleReference.setDeprecated(Boolean.parseBoolean(ruleElement.getAttribute("deprecated")));
481 }
482 if (ruleElement.hasAttribute("name")) {
483 ruleReference.setName(ruleElement.getAttribute("name"));
484 }
485 if (ruleElement.hasAttribute("message")) {
486 ruleReference.setMessage(ruleElement.getAttribute("message"));
487 }
488 if (ruleElement.hasAttribute("externalInfoUrl")) {
489 ruleReference.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
490 }
491 for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
492 Node node = ruleElement.getChildNodes().item(i);
493 if (node.getNodeType() == Node.ELEMENT_NODE) {
494 if (node.getNodeName().equals("description")) {
495 ruleReference.setDescription(parseTextNode(node));
496 } else if (node.getNodeName().equals("example")) {
497 ruleReference.addExample(parseTextNode(node));
498 } else if (node.getNodeName().equals("priority")) {
499 ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node))));
500 } else if (node.getNodeName().equals("properties")) {
501 parsePropertiesNode(ruleReference, node);
502 } else {
503 throw new IllegalArgumentException("Unexpected element <" + node.getNodeName()
504 + "> encountered as child of <rule> element for Rule " + ruleReference.getName());
505 }
506 }
507 }
508
509 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName())
510 || referencedRule.getPriority().compareTo(minimumPriority) <= 0) {
511 ruleSet.addRule(ruleReference);
512 }
513 }
514
515 private static boolean isElementNode(Node node, String name) {
516 return node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(name);
517 }
518
519
520
521
522
523
524 private static void parsePropertiesNode(Rule rule, Node propertiesNode) {
525 for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) {
526 Node node = propertiesNode.getChildNodes().item(i);
527 if (isElementNode(node, "property")) {
528 parsePropertyNodeBR(rule, node);
529 }
530 }
531 }
532
533 private static String valueFrom(Node parentNode) {
534
535 final NodeList nodeList = parentNode.getChildNodes();
536
537 for (int i = 0; i < nodeList.getLength(); i++) {
538 Node node = nodeList.item(i);
539 if (isElementNode(node, "value")) {
540 return parseTextNode(node);
541 }
542 }
543 return null;
544 }
545
546
547
548
549
550
551
552 @SuppressWarnings("unchecked")
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588 private static void setValue(Rule rule, PropertyDescriptor desc, String strValue) {
589 Object realValue = desc.valueFrom(strValue);
590 rule.setProperty(desc, realValue);
591 }
592
593 @SuppressWarnings("unchecked")
594 private static void parsePropertyNodeBR(Rule rule, Node propertyNode) {
595
596 Element propertyElement = (Element) propertyNode;
597 String typeId = propertyElement.getAttribute(PropertyDescriptorFields.TYPE);
598 String strValue = propertyElement.getAttribute(PropertyDescriptorFields.VALUE);
599 if (StringUtil.isEmpty(strValue)) {
600 strValue = valueFrom(propertyElement);
601 }
602
603
604 if (StringUtil.isEmpty(typeId)) {
605 String name = propertyElement.getAttribute(PropertyDescriptorFields.NAME);
606
607 PropertyDescriptor<?> propertyDescriptor = rule.getPropertyDescriptor(name);
608 if (propertyDescriptor == null) {
609 throw new IllegalArgumentException("Cannot set non-existant property '" + name + "' on Rule " + rule.getName());
610 } else {
611 setValue(rule, propertyDescriptor, strValue);
612 }
613 return;
614 }
615
616 net.sourceforge.pmd.PropertyDescriptorFactory pdFactory = PropertyDescriptorUtil.factoryFor(typeId);
617 if (pdFactory == null) {
618 throw new RuntimeException("No property descriptor factory for type: " + typeId);
619 }
620
621 Map<String, Boolean> valueKeys = pdFactory.expectedFields();
622 Map<String, String> values = new HashMap<String, String>(valueKeys.size());
623
624
625 for (Map.Entry<String, Boolean> entry : valueKeys.entrySet()) {
626 String valueStr = propertyElement.getAttribute(entry.getKey());
627 if (entry.getValue() && StringUtil.isEmpty(valueStr)) {
628 System.out.println("Missing required value for: " + entry.getKey());
629 }
630 values.put(entry.getKey(), valueStr);
631 }
632 try {
633 PropertyDescriptor<?> desc = pdFactory.createWith(values);
634 PropertyDescriptorWrapper<?> wrapper = new PropertyDescriptorWrapper(desc);
635
636 rule.definePropertyDescriptor(wrapper);
637 setValue(rule, desc, strValue);
638
639 } catch (Exception ex) {
640 System.out.println("oops");
641 }
642 }
643
644
645
646
647
648
649
650 private static String parseTextNode(Node node) {
651
652 final int nodeCount = node.getChildNodes().getLength();
653 if (nodeCount == 0) {
654 return "";
655 }
656
657 StringBuilder buffer = new StringBuilder();
658
659 for (int i = 0; i < nodeCount; i++) {
660 Node childNode = node.getChildNodes().item(i);
661 if (childNode.getNodeType() == Node.CDATA_SECTION_NODE || childNode.getNodeType() == Node.TEXT_NODE) {
662 buffer.append(childNode.getNodeValue());
663 }
664 }
665 return buffer.toString();
666 }
667
668
669
670
671
672
673
674 private boolean isRuleName(Element ruleElement, String ruleName) {
675 if (ruleElement.hasAttribute("name")) {
676 return ruleElement.getAttribute("name").equals(ruleName);
677 } else if (ruleElement.hasAttribute("ref")) {
678 RuleSetReferenceId ruleSetReferenceId = RuleSetReferenceId.parse(ruleElement.getAttribute("ref")).get(0);
679 return ruleSetReferenceId.getRuleName() != null && ruleSetReferenceId.getRuleName().equals(ruleName);
680 } else {
681 return false;
682 }
683 }
684 }