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 String priority = null;
284 NodeList childNodes = ruleElement.getChildNodes();
285 for (int i = 0; i < childNodes.getLength(); i++) {
286 Node child = childNodes.item(i);
287 if (isElementNode(child,"exclude")) {
288 Element excludeElement = (Element) child;
289 ruleSetReference.addExclude(excludeElement.getAttribute("name"));
290 } else if (isElementNode(child, "priority")) {
291 priority = parseTextNode(child).trim();
292 }
293 }
294
295 RuleSetFactory ruleSetFactory = new RuleSetFactory();
296 ruleSetFactory.setClassLoader(classLoader);
297 RuleSet otherRuleSet = ruleSetFactory.createRuleSet(RuleSetReferenceId.parse(ref).get(0));
298 for (Rule rule : otherRuleSet.getRules()) {
299 if (!ruleSetReference.getExcludes().contains(rule.getName())
300 && rule.getPriority().compareTo(minimumPriority) <= 0 && !rule.isDeprecated()) {
301 RuleReference ruleReference = new RuleReference();
302 ruleReference.setRuleSetReference(ruleSetReference);
303 ruleReference.setRule(rule);
304 ruleSet.addRuleIfNotExists(ruleReference);
305
306
307 if (priority != null) {
308 ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(priority)));
309 }
310 }
311 }
312 }
313
314
315
316
317
318
319
320
321
322 private void parseSingleRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode)
323 throws ClassNotFoundException, InstantiationException, IllegalAccessException {
324 Element ruleElement = (Element) ruleNode;
325
326
327 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName())
328 && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) {
329 return;
330 }
331
332 String attribute = ruleElement.getAttribute("class");
333 Class<?> c = classLoader.loadClass(attribute);
334 Rule rule = (Rule) c.newInstance();
335
336 rule.setName(ruleElement.getAttribute("name"));
337
338 if (ruleElement.hasAttribute("language")) {
339 String languageName = ruleElement.getAttribute("language");
340 Language language = Language.findByTerseName(languageName);
341 if (language == null) {
342 throw new IllegalArgumentException("Unknown Language '" + languageName + "' for Rule " + rule.getName()
343 + ", supported Languages are "
344 + Language.commaSeparatedTerseNames(Language.findWithRuleSupport()));
345 }
346 rule.setLanguage(language);
347 }
348
349 Language language = rule.getLanguage();
350 if (language == null) {
351 throw new IllegalArgumentException("Rule " + rule.getName()
352 + " does not have a Language; missing 'language' attribute?");
353 }
354
355 if (ruleElement.hasAttribute("minimumLanguageVersion")) {
356 String minimumLanguageVersionName = ruleElement.getAttribute("minimumLanguageVersion");
357 LanguageVersion minimumLanguageVersion = language.getVersion(minimumLanguageVersionName);
358 if (minimumLanguageVersion == null) {
359 throw new IllegalArgumentException("Unknown minimum Language Version '" + minimumLanguageVersionName
360 + "' for Language '" + language.getTerseName() + "' for Rule " + rule.getName()
361 + "; supported Language Versions are: "
362 + LanguageVersion.commaSeparatedTerseNames(language.getVersions()));
363 }
364 rule.setMinimumLanguageVersion(minimumLanguageVersion);
365 }
366
367 if (ruleElement.hasAttribute("maximumLanguageVersion")) {
368 String maximumLanguageVersionName = ruleElement.getAttribute("maximumLanguageVersion");
369 LanguageVersion maximumLanguageVersion = language.getVersion(maximumLanguageVersionName);
370 if (maximumLanguageVersion == null) {
371 throw new IllegalArgumentException("Unknown maximum Language Version '" + maximumLanguageVersionName
372 + "' for Language '" + language.getTerseName() + "' for Rule " + rule.getName()
373 + "; supported Language Versions are: "
374 + LanguageVersion.commaSeparatedTerseNames(language.getVersions()));
375 }
376 rule.setMaximumLanguageVersion(maximumLanguageVersion);
377 }
378
379 if (rule.getMinimumLanguageVersion() != null && rule.getMaximumLanguageVersion() != null) {
380 throw new IllegalArgumentException("The minimum Language Version '"
381 + rule.getMinimumLanguageVersion().getTerseName()
382 + "' must be prior to the maximum Language Version '"
383 + rule.getMaximumLanguageVersion().getTerseName() + "' for Rule " + rule.getName()
384 + "; perhaps swap them around?");
385 }
386
387 String since = ruleElement.getAttribute("since");
388 if (StringUtil.isNotEmpty(since)) {
389 rule.setSince(since);
390 }
391 rule.setMessage(ruleElement.getAttribute("message"));
392 rule.setRuleSetName(ruleSet.getName());
393 rule.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
394
395 if (hasAttributeSetTrue(ruleElement,"dfa")) {
396 rule.setUsesDFA();
397 }
398
399 if (hasAttributeSetTrue(ruleElement,"typeResolution")) {
400 rule.setUsesTypeResolution();
401 }
402
403 final NodeList nodeList = ruleElement.getChildNodes();
404 for (int i = 0; i < nodeList.getLength(); i++) {
405 Node node = nodeList.item(i);
406 if (node.getNodeType() != Node.ELEMENT_NODE) { continue; }
407 String nodeName = node.getNodeName();
408 if (nodeName.equals("description")) {
409 rule.setDescription(parseTextNode(node));
410 } else if (nodeName.equals("example")) {
411 rule.addExample(parseTextNode(node));
412 } else if (nodeName.equals("priority")) {
413 rule.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node).trim())));
414 } else if (nodeName.equals("properties")) {
415 parsePropertiesNode(rule, node);
416 } else {
417 throw new IllegalArgumentException("Unexpected element <" + nodeName
418 + "> encountered as child of <rule> element for Rule " + rule.getName());
419 }
420
421 }
422 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName()) || rule.getPriority().compareTo(minimumPriority) <= 0) {
423 ruleSet.addRule(rule);
424 }
425 }
426
427 private static boolean hasAttributeSetTrue(Element element, String attributeId) {
428 return element.hasAttribute(attributeId) && "true".equalsIgnoreCase(element.getAttribute(attributeId));
429 }
430
431
432
433
434
435
436
437
438
439
440
441 private void parseRuleReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode, String ref) throws RuleSetNotFoundException {
442 Element ruleElement = (Element) ruleNode;
443
444
445 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName())
446 && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) {
447 return;
448 }
449
450 RuleSetFactory ruleSetFactory = new RuleSetFactory();
451 ruleSetFactory.setClassLoader(classLoader);
452
453 RuleSetReferenceId otherRuleSetReferenceId = RuleSetReferenceId.parse(ref).get(0);
454 if (!otherRuleSetReferenceId.isExternal()) {
455 otherRuleSetReferenceId = new RuleSetReferenceId(ref, ruleSetReferenceId);
456 }
457 Rule referencedRule = ruleSetFactory.createRule(otherRuleSetReferenceId);
458 if (referencedRule == null) {
459 throw new IllegalArgumentException("Unable to find referenced rule "
460 + otherRuleSetReferenceId.getRuleName() + "; perhaps the rule name is mispelled?");
461 }
462
463 if (warnDeprecated && referencedRule.isDeprecated()) {
464 if (referencedRule instanceof RuleReference) {
465 RuleReference ruleReference = (RuleReference) referencedRule;
466 LOG.warning("Use Rule name " + ruleReference.getRuleSetReference().getRuleSetFileName() + "/"
467 + ruleReference.getName() + " instead of the deprecated Rule name " + otherRuleSetReferenceId
468 + ". Future versions of PMD will remove support for this deprecated Rule name usage.");
469 } else if (referencedRule instanceof MockRule) {
470 LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId
471 + " as it has been removed from PMD and no longer functions."
472 + " Future versions of PMD will remove support for this Rule.");
473 } else {
474 LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId
475 + " as it is scheduled for removal from PMD."
476 + " Future versions of PMD will remove support for this Rule.");
477 }
478 }
479
480 RuleSetReference ruleSetReference = new RuleSetReference();
481 ruleSetReference.setAllRules(false);
482 ruleSetReference.setRuleSetFileName(otherRuleSetReferenceId.getRuleSetFileName());
483
484 RuleReference ruleReference = new RuleReference();
485 ruleReference.setRuleSetReference(ruleSetReference);
486 ruleReference.setRule(referencedRule);
487
488 if (ruleElement.hasAttribute("deprecated")) {
489 ruleReference.setDeprecated(Boolean.parseBoolean(ruleElement.getAttribute("deprecated")));
490 }
491 if (ruleElement.hasAttribute("name")) {
492 ruleReference.setName(ruleElement.getAttribute("name"));
493 }
494 if (ruleElement.hasAttribute("message")) {
495 ruleReference.setMessage(ruleElement.getAttribute("message"));
496 }
497 if (ruleElement.hasAttribute("externalInfoUrl")) {
498 ruleReference.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
499 }
500 for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
501 Node node = ruleElement.getChildNodes().item(i);
502 if (node.getNodeType() == Node.ELEMENT_NODE) {
503 if (node.getNodeName().equals("description")) {
504 ruleReference.setDescription(parseTextNode(node));
505 } else if (node.getNodeName().equals("example")) {
506 ruleReference.addExample(parseTextNode(node));
507 } else if (node.getNodeName().equals("priority")) {
508 ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node))));
509 } else if (node.getNodeName().equals("properties")) {
510 parsePropertiesNode(ruleReference, node);
511 } else {
512 throw new IllegalArgumentException("Unexpected element <" + node.getNodeName()
513 + "> encountered as child of <rule> element for Rule " + ruleReference.getName());
514 }
515 }
516 }
517
518 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName())
519 || referencedRule.getPriority().compareTo(minimumPriority) <= 0) {
520 ruleSet.addRuleReplaceIfExists(ruleReference);
521 }
522 }
523
524 private static boolean isElementNode(Node node, String name) {
525 return node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(name);
526 }
527
528
529
530
531
532
533 private static void parsePropertiesNode(Rule rule, Node propertiesNode) {
534 for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) {
535 Node node = propertiesNode.getChildNodes().item(i);
536 if (isElementNode(node, "property")) {
537 parsePropertyNodeBR(rule, node);
538 }
539 }
540 }
541
542 private static String valueFrom(Node parentNode) {
543
544 final NodeList nodeList = parentNode.getChildNodes();
545
546 for (int i = 0; i < nodeList.getLength(); i++) {
547 Node node = nodeList.item(i);
548 if (isElementNode(node, "value")) {
549 return parseTextNode(node);
550 }
551 }
552 return null;
553 }
554
555
556
557
558
559
560
561 @SuppressWarnings("unchecked")
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
589
590
591
592
593
594
595
596
597 private static void setValue(Rule rule, PropertyDescriptor desc, String strValue) {
598 Object realValue = desc.valueFrom(strValue);
599 rule.setProperty(desc, realValue);
600 }
601
602 @SuppressWarnings("unchecked")
603 private static void parsePropertyNodeBR(Rule rule, Node propertyNode) {
604
605 Element propertyElement = (Element) propertyNode;
606 String typeId = propertyElement.getAttribute(PropertyDescriptorFields.TYPE);
607 String strValue = propertyElement.getAttribute(PropertyDescriptorFields.VALUE);
608 if (StringUtil.isEmpty(strValue)) {
609 strValue = valueFrom(propertyElement);
610 }
611
612
613 if (StringUtil.isEmpty(typeId)) {
614 String name = propertyElement.getAttribute(PropertyDescriptorFields.NAME);
615
616 PropertyDescriptor<?> propertyDescriptor = rule.getPropertyDescriptor(name);
617 if (propertyDescriptor == null) {
618 throw new IllegalArgumentException("Cannot set non-existant property '" + name + "' on Rule " + rule.getName());
619 } else {
620 setValue(rule, propertyDescriptor, strValue);
621 }
622 return;
623 }
624
625 net.sourceforge.pmd.PropertyDescriptorFactory pdFactory = PropertyDescriptorUtil.factoryFor(typeId);
626 if (pdFactory == null) {
627 throw new RuntimeException("No property descriptor factory for type: " + typeId);
628 }
629
630 Map<String, Boolean> valueKeys = pdFactory.expectedFields();
631 Map<String, String> values = new HashMap<String, String>(valueKeys.size());
632
633
634 for (Map.Entry<String, Boolean> entry : valueKeys.entrySet()) {
635 String valueStr = propertyElement.getAttribute(entry.getKey());
636 if (entry.getValue() && StringUtil.isEmpty(valueStr)) {
637 System.out.println("Missing required value for: " + entry.getKey());
638 }
639 values.put(entry.getKey(), valueStr);
640 }
641 try {
642 PropertyDescriptor<?> desc = pdFactory.createWith(values);
643 PropertyDescriptorWrapper<?> wrapper = new PropertyDescriptorWrapper(desc);
644
645 rule.definePropertyDescriptor(wrapper);
646 setValue(rule, desc, strValue);
647
648 } catch (Exception ex) {
649 System.out.println("oops");
650 }
651 }
652
653
654
655
656
657
658
659 private static String parseTextNode(Node node) {
660
661 final int nodeCount = node.getChildNodes().getLength();
662 if (nodeCount == 0) {
663 return "";
664 }
665
666 StringBuilder buffer = new StringBuilder();
667
668 for (int i = 0; i < nodeCount; i++) {
669 Node childNode = node.getChildNodes().item(i);
670 if (childNode.getNodeType() == Node.CDATA_SECTION_NODE || childNode.getNodeType() == Node.TEXT_NODE) {
671 buffer.append(childNode.getNodeValue());
672 }
673 }
674 return buffer.toString();
675 }
676
677
678
679
680
681
682
683 private boolean isRuleName(Element ruleElement, String ruleName) {
684 if (ruleElement.hasAttribute("name")) {
685 return ruleElement.getAttribute("name").equals(ruleName);
686 } else if (ruleElement.hasAttribute("ref")) {
687 RuleSetReferenceId ruleSetReferenceId = RuleSetReferenceId.parse(ruleElement.getAttribute("ref")).get(0);
688 return ruleSetReferenceId.getRuleName() != null && ruleSetReferenceId.getRuleName().equals(ruleName);
689 } else {
690 return false;
691 }
692 }
693 }