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