View Javadoc

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 		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 	 * Parse a rule node as a single Rule.  The Rule has been fully defined within
307 	 * the context of the current RuleSet.
308 	 *
309 	 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
310 	 * @param ruleSet The RuleSet being constructed.
311 	 * @param ruleNode Must be a rule element node.
312 	 */
313 	private void parseSingleRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode)
314 	throws ClassNotFoundException, InstantiationException, IllegalAccessException {
315 		Element ruleElement = (Element) ruleNode;
316 
317 		// Stop if we're looking for a particular Rule, and this element is not it.
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 	 * Parse a rule node as a RuleReference.  A RuleReference is a single Rule
424 	 * which comes from another RuleSet with some of it's attributes potentially
425 	 * overridden.
426 	 *
427 	 * @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
428 	 * @param ruleSet The RuleSet being constructed.
429 	 * @param ruleNode Must be a rule element node.
430 	 * @param ref A reference to a Rule.
431 	 */
432 	private void parseRuleReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode, String ref) throws RuleSetNotFoundException {
433 		Element ruleElement = (Element) ruleNode;
434 
435 		// Stop if we're looking for a particular Rule, and this element is not it.
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 	 * Parse a properties node.
520 	 *
521 	 * @param rule The Rule to which the properties should be added. 
522 	 * @param propertiesNode Must be a properties element node.
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 	 * Parse a property node.
548 	 *
549 	 * @param rule The Rule to which the property should be added. 
550 	 * @param propertyNode Must be a property element node.
551 	 */
552 	@SuppressWarnings("unchecked")
553 //	private static void parsePropertyNode(Rule rule, Node propertyNode) {
554 //		Element propertyElement = (Element) propertyNode;
555 //		String name = propertyElement.getAttribute("name");
556 //		String description = propertyElement.getAttribute("description");
557 //		String type = propertyElement.getAttribute("type");
558 //		String delimiter = propertyElement.getAttribute("delimiter");
559 //		String min = propertyElement.getAttribute("min");
560 //		String max = propertyElement.getAttribute("max");
561 //		String value = propertyElement.getAttribute("value");
562 //
563 //		// If value not provided, get from child <value> element.
564 //		if (StringUtil.isEmpty(value)) {
565 //			for (int i = 0; i < propertyNode.getChildNodes().getLength(); i++) {
566 //				Node node = propertyNode.getChildNodes().item(i);
567 //				if ((node.getNodeType() == Node.ELEMENT_NODE) && node.getNodeName().equals("value")) {
568 //					value = parseTextNode(node);
569 //				}
570 //			}
571 //		}
572 //
573 //		// Setting of existing property, or defining a new property?
574 //		if (StringUtil.isEmpty(type)) {
575 //			PropertyDescriptor propertyDescriptor = rule.getPropertyDescriptor(name);
576 //			if (propertyDescriptor == null) {
577 //				throw new IllegalArgumentException("Cannot set non-existant property '" + name + "' on Rule " + rule.getName());
578 //			} else {
579 //				Object realValue = propertyDescriptor.valueFrom(value);
580 //				rule.setProperty(propertyDescriptor, realValue);
581 //			}
582 //		} else {
583 //			PropertyDescriptor propertyDescriptor = PropertyDescriptorFactory.createPropertyDescriptor(name,  description, type, delimiter, min, max, value);
584 //			rule.definePropertyDescriptor(propertyDescriptor);
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 		// Setting of existing property, or defining a new property?
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 		// populate a map of values for an individual descriptor
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());	// debug pt  TODO
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");		// debug pt  TODO
641 		}
642 	}
643 
644 	/**
645 	 * Parse a String from a textually type node.
646 	 *
647 	 * @param node The node.
648 	 * @return The String.
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 	 * Determine if the specified rule element will represent a Rule with the given name. 
670 	 * @param ruleElement The rule element.
671 	 * @param ruleName The Rule name.
672 	 * @return <code>true</code> if the Rule would have the given name, <code>false</code> otherwise.
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 }