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