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