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