RuleSetFactory xref
1 /**
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
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 * RuleSetFactory is responsible for creating RuleSet instances from XML content.
37 * By default Rules will be loaded using the ClassLoader for this class, using
38 * the {@link RulePriority#LOW} priority, with Rule deprecation warnings off.
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 * Set the ClassLoader to use when loading Rules.
50 *
51 * @param classLoader The ClassLoader to use.
52 */
53 public void setClassLoader(ClassLoader classLoader) {
54 this.classLoader = classLoader;
55 }
56
57 /**
58 * Set the minimum rule priority threshold for all Rules which are loaded
59 * from RuleSets via reference.
60 *
61 * @param minimumPriority The minimum priority.
62 */
63 public void setMinimumPriority(RulePriority minimumPriority) {
64 this.minimumPriority = minimumPriority;
65 }
66
67 /**
68 * Set whether warning messages should be logged for usage of deprecated Rules.
69 * @param warnDeprecated <code>true</code> to log warning messages.
70 */
71 public void setWarnDeprecated(boolean warnDeprecated) {
72 this.warnDeprecated = warnDeprecated;
73 }
74
75 /**
76 * Returns an Iterator of RuleSet objects loaded from descriptions from the
77 * "rulesets.properties" resource for each Language with Rule support.
78 *
79 * @return An Iterator of RuleSet objects.
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 * Create a RuleSets from a comma separated list of RuleSet reference IDs. This is a
102 * convenience method which calls {@link RuleSetReferenceId#parse(String)}, and then calls
103 * {@link #createRuleSets(List)}.
104 * The currently configured ClassLoader is used.
105 *
106 * @param referenceString A comma separated list of RuleSet reference IDs.
107 * @return The new RuleSets.
108 * @throws RuleSetNotFoundException if unable to find a resource.
109 */
110 public synchronized RuleSets createRuleSets(String referenceString) throws RuleSetNotFoundException {
111 return createRuleSets(RuleSetReferenceId.parse(referenceString));
112 }
113
114 /**
115 * Create a RuleSets from a list of RuleSetReferenceIds.
116 * The currently configured ClassLoader is used.
117 *
118 * @param ruleSetReferenceIds The List of RuleSetReferenceId of the RuleSets to create.
119 * @return The new RuleSets.
120 * @throws RuleSetNotFoundException if unable to find a resource.
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 * Create a RuleSet from a RuleSet reference ID string. This is a
134 * convenience method which calls {@link RuleSetReferenceId#parse(String)}, gets the first
135 * item in the List, and then calls {@link #createRuleSet(RuleSetReferenceId)}.
136 * The currently configured ClassLoader is used.
137 *
138 * @param referenceString A comma separated list of RuleSet reference IDs.
139 * @return A new RuleSet.
140 * @throws RuleSetNotFoundException if unable to find a resource.
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 * Create a RuleSet from a RuleSetReferenceId. Priority filtering is ignored when loading
153 * a single Rule.
154 * The currently configured ClassLoader is used.
155 *
156 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet to create.
157 * @return A new RuleSet.
158 * @throws RuleSetNotFoundException if unable to find a resource.
159 */
160 public synchronized RuleSet createRuleSet(RuleSetReferenceId ruleSetReferenceId) throws RuleSetNotFoundException {
161 return parseRuleSetNode(ruleSetReferenceId, ruleSetReferenceId.getInputStream(this.classLoader));
162 }
163
164 /**
165 * Create a Rule from a RuleSet created from a file name resource.
166 * The currently configured ClassLoader is used.
167 * <p>
168 * Any Rules in the RuleSet other than the one being created, are _not_ created.
169 *
170 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet with the Rule to create.
171 * @return A new Rule.
172 * @throws RuleSetNotFoundException if unable to find a resource.
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 * Parse a ruleset node to construct a RuleSet.
185 *
186 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
187 * @param inputStream InputStream containing the RuleSet XML configuration.
188 * @return The new RuleSet.
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 * Parse a rule node.
249 *
250 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
251 * @param ruleSet The RuleSet being constructed.
252 * @param ruleNode Must be a rule element node.
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 * Parse a rule node as an RuleSetReference for all Rules. Every Rule from
269 * the referred to RuleSet will be added as a RuleReference except for those
270 * explicitly excluded, below the minimum priority threshold for this
271 * RuleSetFactory, or which are deprecated.
272 *
273 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
274 * @param ruleSet The RuleSet being constructed.
275 * @param ruleElement Must be a rule element node.
276 * @param ref The RuleSet reference.
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 // override the priority
307 if (priority != null) {
308 ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(priority)));
309 }
310 }
311 }
312 }
313
314 /**
315 * Parse a rule node as a single Rule. The Rule has been fully defined within
316 * the context of the current RuleSet.
317 *
318 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
319 * @param ruleSet The RuleSet being constructed.
320 * @param ruleNode Must be a rule element node.
321 */
322 private void parseSingleRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode)
323 throws ClassNotFoundException, InstantiationException, IllegalAccessException {
324 Element ruleElement = (Element) ruleNode;
325
326 // Stop if we're looking for a particular Rule, and this element is not it.
327 if (StringUtil.isNotEmpty(ruleSetReferenceId.getRuleName())
328 && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) {
329 return;
330 }
331
332 String attribute = ruleElement.getAttribute("class");
333 if ( attribute == null || "".equals(attribute))
334 throw new IllegalArgumentException("The 'class' field of rule can't be null, nor empty.");
335 Rule rule = (Rule) classLoader.loadClass(attribute).newInstance();
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 * Parse a rule node as a RuleReference. A RuleReference is a single Rule
433 * which comes from another RuleSet with some of it's attributes potentially
434 * overridden.
435 *
436 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
437 * @param ruleSet The RuleSet being constructed.
438 * @param ruleNode Must be a rule element node.
439 * @param ref A reference to a Rule.
440 */
441 private void parseRuleReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode, String ref) throws RuleSetNotFoundException {
442 Element ruleElement = (Element) ruleNode;
443
444 // Stop if we're looking for a particular Rule, and this element is not it.
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 * Parse a properties node.
529 *
530 * @param rule The Rule to which the properties should be added.
531 * @param propertiesNode Must be a properties element node.
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 * Parse a property node.
557 *
558 * @param rule The Rule to which the property should be added.
559 * @param propertyNode Must be a property element node.
560 */
561 @SuppressWarnings("unchecked")
562 // private static void parsePropertyNode(Rule rule, Node propertyNode) {
563 // Element propertyElement = (Element) propertyNode;
564 // String name = propertyElement.getAttribute("name");
565 // String description = propertyElement.getAttribute("description");
566 // String type = propertyElement.getAttribute("type");
567 // String delimiter = propertyElement.getAttribute("delimiter");
568 // String min = propertyElement.getAttribute("min");
569 // String max = propertyElement.getAttribute("max");
570 // String value = propertyElement.getAttribute("value");
571 //
572 // // If value not provided, get from child <value> element.
573 // if (StringUtil.isEmpty(value)) {
574 // for (int i = 0; i < propertyNode.getChildNodes().getLength(); i++) {
575 // Node node = propertyNode.getChildNodes().item(i);
576 // if ((node.getNodeType() == Node.ELEMENT_NODE) && node.getNodeName().equals("value")) {
577 // value = parseTextNode(node);
578 // }
579 // }
580 // }
581 //
582 // // Setting of existing property, or defining a new property?
583 // if (StringUtil.isEmpty(type)) {
584 // PropertyDescriptor propertyDescriptor = rule.getPropertyDescriptor(name);
585 // if (propertyDescriptor == null) {
586 // throw new IllegalArgumentException("Cannot set non-existant property '" + name + "' on Rule " + rule.getName());
587 // } else {
588 // Object realValue = propertyDescriptor.valueFrom(value);
589 // rule.setProperty(propertyDescriptor, realValue);
590 // }
591 // } else {
592 // PropertyDescriptor propertyDescriptor = PropertyDescriptorFactory.createPropertyDescriptor(name, description, type, delimiter, min, max, value);
593 // rule.definePropertyDescriptor(propertyDescriptor);
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 // Setting of existing property, or defining a new property?
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 // populate a map of values for an individual descriptor
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()); // debug pt TODO
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"); // debug pt TODO
650 }
651 }
652
653 /**
654 * Parse a String from a textually type node.
655 *
656 * @param node The node.
657 * @return The String.
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 * Determine if the specified rule element will represent a Rule with the given name.
679 * @param ruleElement The rule element.
680 * @param ruleName The Rule name.
681 * @return <code>true</code> if the Rule would have the given name, <code>false</code> otherwise.
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 }