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 		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() && containsRule(ruleSetReferenceId, otherRuleSetReferenceId.getRuleName())) {
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     /**
525      * Check whether the given ruleName is contained in the given ruleset.
526      * @param ruleSetReferenceId the ruleset to check
527      * @param ruleName the rule name to search for
528      * @return <code>true</code> if the ruleName exists
529      */
530     private boolean containsRule(RuleSetReferenceId ruleSetReferenceId, String ruleName) {
531         boolean found = false;
532         try {
533             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
534             Document document = builder.parse(ruleSetReferenceId.getInputStream(classLoader));
535             Element ruleSetElement = document.getDocumentElement();
536 
537             NodeList rules = ruleSetElement.getElementsByTagName("rule");
538             for (int i = 0; i < rules.getLength(); i++) {
539                 Element rule = (Element)rules.item(i);
540                 if (rule.hasAttribute("name")) {
541                     if (rule.getAttribute("name").equals(ruleName)) {
542                         found = true;
543                         break;
544                     }
545                 }
546             }
547         } catch (Exception e) {
548             throw new RuntimeException(e);
549         }
550 
551         return found;
552     }
553 
554     private static boolean isElementNode(Node node, String name) {
555 		return node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(name);
556 	}
557 	/**
558 	 * Parse a properties node.
559 	 *
560 	 * @param rule The Rule to which the properties should be added. 
561 	 * @param propertiesNode Must be a properties element node.
562 	 */
563 	private static void parsePropertiesNode(Rule rule, Node propertiesNode) {
564 		for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) {
565 			Node node = propertiesNode.getChildNodes().item(i);
566 			if (isElementNode(node, "property")) {
567 				parsePropertyNodeBR(rule, node);
568 			}
569 		}
570 	}
571 
572 	private static String valueFrom(Node parentNode) {
573 
574 		final NodeList nodeList = parentNode.getChildNodes();
575 
576 		for (int i = 0; i < nodeList.getLength(); i++) {
577 			Node node = nodeList.item(i);
578 			if (isElementNode(node, "value")) {
579 				return parseTextNode(node);
580 			}
581 		}
582 		return null;
583 	}
584 
585 	/**
586 	 * Parse a property node.
587 	 *
588 	 * @param rule The Rule to which the property should be added. 
589 	 * @param propertyNode Must be a property element node.
590 	 */
591 	@SuppressWarnings("unchecked")
592 //	private static void parsePropertyNode(Rule rule, Node propertyNode) {
593 //		Element propertyElement = (Element) propertyNode;
594 //		String name = propertyElement.getAttribute("name");
595 //		String description = propertyElement.getAttribute("description");
596 //		String type = propertyElement.getAttribute("type");
597 //		String delimiter = propertyElement.getAttribute("delimiter");
598 //		String min = propertyElement.getAttribute("min");
599 //		String max = propertyElement.getAttribute("max");
600 //		String value = propertyElement.getAttribute("value");
601 //
602 //		// If value not provided, get from child <value> element.
603 //		if (StringUtil.isEmpty(value)) {
604 //			for (int i = 0; i < propertyNode.getChildNodes().getLength(); i++) {
605 //				Node node = propertyNode.getChildNodes().item(i);
606 //				if ((node.getNodeType() == Node.ELEMENT_NODE) && node.getNodeName().equals("value")) {
607 //					value = parseTextNode(node);
608 //				}
609 //			}
610 //		}
611 //
612 //		// Setting of existing property, or defining a new property?
613 //		if (StringUtil.isEmpty(type)) {
614 //			PropertyDescriptor propertyDescriptor = rule.getPropertyDescriptor(name);
615 //			if (propertyDescriptor == null) {
616 //				throw new IllegalArgumentException("Cannot set non-existant property '" + name + "' on Rule " + rule.getName());
617 //			} else {
618 //				Object realValue = propertyDescriptor.valueFrom(value);
619 //				rule.setProperty(propertyDescriptor, realValue);
620 //			}
621 //		} else {
622 //			PropertyDescriptor propertyDescriptor = PropertyDescriptorFactory.createPropertyDescriptor(name,  description, type, delimiter, min, max, value);
623 //			rule.definePropertyDescriptor(propertyDescriptor);
624 //		}
625 //	}
626 
627 	private static void setValue(Rule rule, PropertyDescriptor desc, String strValue) {
628 		Object realValue = desc.valueFrom(strValue);
629 		rule.setProperty(desc, realValue);
630 	}
631 
632 	@SuppressWarnings("unchecked")
633 	private static void parsePropertyNodeBR(Rule rule, Node propertyNode) {
634 
635 		Element propertyElement = (Element) propertyNode;
636 		String typeId = propertyElement.getAttribute(PropertyDescriptorFields.TYPE);
637 		String strValue = propertyElement.getAttribute(PropertyDescriptorFields.VALUE);
638 		if (StringUtil.isEmpty(strValue)) {
639 			strValue = valueFrom(propertyElement);
640 		}
641 
642 		// Setting of existing property, or defining a new property?
643 		if (StringUtil.isEmpty(typeId)) {
644 			String name = propertyElement.getAttribute(PropertyDescriptorFields.NAME);
645 
646 			PropertyDescriptor<?> propertyDescriptor = rule.getPropertyDescriptor(name);
647 			if (propertyDescriptor == null) {
648 				throw new IllegalArgumentException("Cannot set non-existant property '" + name + "' on Rule " + rule.getName());
649 			} else {
650 				setValue(rule, propertyDescriptor, strValue);
651 			}
652 			return;
653 		}
654 
655 		net.sourceforge.pmd.PropertyDescriptorFactory pdFactory = PropertyDescriptorUtil.factoryFor(typeId);
656 		if (pdFactory == null) {
657 			throw new RuntimeException("No property descriptor factory for type: " + typeId);
658 		}
659 
660 		Map<String, Boolean> valueKeys = pdFactory.expectedFields();
661 		Map<String, String> values = new HashMap<String, String>(valueKeys.size());
662 
663 		// populate a map of values for an individual descriptor
664 		for (Map.Entry<String, Boolean> entry : valueKeys.entrySet()) {
665 			String valueStr = propertyElement.getAttribute(entry.getKey());
666 			if (entry.getValue() && StringUtil.isEmpty(valueStr)) {
667 				System.out.println("Missing required value for: " + entry.getKey());	// debug pt  TODO
668 			}
669 			values.put(entry.getKey(), valueStr);
670 		}
671 		try {
672 			PropertyDescriptor<?> desc = pdFactory.createWith(values);
673 			PropertyDescriptorWrapper<?> wrapper = new PropertyDescriptorWrapper(desc);
674 
675 			rule.definePropertyDescriptor(wrapper);
676 			setValue(rule, desc, strValue);
677 
678 		} catch (Exception ex) {
679 			System.out.println("oops");		// debug pt  TODO
680 		}
681 	}
682 
683 	/**
684 	 * Parse a String from a textually type node.
685 	 *
686 	 * @param node The node.
687 	 * @return The String.
688 	 */
689 	private static String parseTextNode(Node node) {
690 
691 		final int nodeCount = node.getChildNodes().getLength();
692 		if (nodeCount == 0) {
693 			return "";
694 		}
695 
696 		StringBuilder buffer = new StringBuilder();
697 
698 		for (int i = 0; i < nodeCount; i++) {
699 			Node childNode = node.getChildNodes().item(i);
700 			if (childNode.getNodeType() == Node.CDATA_SECTION_NODE || childNode.getNodeType() == Node.TEXT_NODE) {
701 				buffer.append(childNode.getNodeValue());
702 			}
703 		}
704 		return buffer.toString();
705 	}
706 
707 	/**
708 	 * Determine if the specified rule element will represent a Rule with the given name. 
709 	 * @param ruleElement The rule element.
710 	 * @param ruleName The Rule name.
711 	 * @return <code>true</code> if the Rule would have the given name, <code>false</code> otherwise.
712 	 */
713 	private boolean isRuleName(Element ruleElement, String ruleName) {
714 		if (ruleElement.hasAttribute("name")) {
715 			return ruleElement.getAttribute("name").equals(ruleName);
716 		} else if (ruleElement.hasAttribute("ref")) {
717 			RuleSetReferenceId ruleSetReferenceId = RuleSetReferenceId.parse(ruleElement.getAttribute("ref")).get(0);
718 			return ruleSetReferenceId.getRuleName() != null && ruleSetReferenceId.getRuleName().equals(ruleName);
719 		} else {
720 			return false;
721 		}
722 	}
723 }