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 net.sourceforge.pmd.util.ResourceLoader;
    7   import org.w3c.dom.Document;
    8   import org.w3c.dom.Element;
    9   import org.w3c.dom.Node;
    10  import org.w3c.dom.NodeList;
    11  import org.xml.sax.SAXException;
    12
    13  import javax.xml.parsers.DocumentBuilder;
    14  import javax.xml.parsers.DocumentBuilderFactory;
    15  import javax.xml.parsers.ParserConfigurationException;
    16  import java.io.IOException;
    17  import java.io.InputStream;
    18  import java.util.ArrayList;
    19  import java.util.HashSet;
    20  import java.util.Iterator;
    21  import java.util.List;
    22  import java.util.Properties;
    23  import java.util.Set;
    24  import java.util.StringTokenizer;
    25
    26  // Note that ruleset parsing may fail on JDK 1.6 beta
    27  // due to this bug - http://www.netbeans.org/issues/show_bug.cgi?id=63257
    28
    29  public class RuleSetFactory {
    30
    31      private static class OverrideParser {
    32          private Element ruleElement;
    33
    34          public OverrideParser(Element ruleElement) {
    35              this.ruleElement = ruleElement;
    36          }
    37
    38          public void overrideAsNecessary(Rule rule) {
    39              if (ruleElement.hasAttribute("name")) {
    40                  rule.setName(ruleElement.getAttribute("name"));
    41              }
    42              if (ruleElement.hasAttribute("message")) {
    43                  rule.setMessage(ruleElement.getAttribute("message"));
    44              }
    45              if (ruleElement.hasAttribute("externalInfoUrl")) {
    46                  rule.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
    47              }
    48              for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
    49                  Node node = ruleElement.getChildNodes().item(i);
    50                  if (node.getNodeType() == Node.ELEMENT_NODE) {
    51                      if (node.getNodeName().equals("description")) {
    52                          rule.setDescription(parseTextNode(node));
    53                      } else if (node.getNodeName().equals("example")) {
    54                          rule.setExample(parseTextNode(node));
    55                      } else if (node.getNodeName().equals("priority")) {
    56                          rule.setPriority(Integer.parseInt(parseTextNode(node)));
    57                      } else if (node.getNodeName().equals("properties")) {
    58                          Properties p = new Properties();
    59                          parsePropertiesNode(p, node);
    60                          rule.addProperties(p);
    61                      }
    62                  }
    63              }
    64          }
    65      }
    66
    67      private ClassLoader classLoader;
    68      private int minPriority = Rule.LOWEST_PRIORITY;
    69
    70      public void setMinimumPriority(int minPriority) {
    71          this.minPriority = minPriority;
    72      }
    73
    74      /***
    75       * Returns an Iterator of RuleSet objects loaded from descriptions from the
    76       * "rulesets.properties" resource.
    77       *
    78       * @return an iterator of RuleSet objects
    79       */
    80      public Iterator getRegisteredRuleSets() throws RuleSetNotFoundException {
    81          try {
    82              Properties props = new Properties();
    83              props.load(ResourceLoader
    84                      .loadResourceAsStream("rulesets/rulesets.properties"));
    85              String rulesetFilenames = props.getProperty("rulesets.filenames");
    86              List ruleSets = new ArrayList();
    87              for (StringTokenizer st = new StringTokenizer(rulesetFilenames, ","); st
    88                      .hasMoreTokens();) {
    89                  ruleSets.add(createRuleSet(st.nextToken()));
    90              }
    91              return ruleSets.iterator();
    92          } catch (IOException ioe) {
    93              throw new RuntimeException("Couldn't find rulesets.properties; please ensure that the rulesets directory is on the classpath.  Here's the current classpath: "
    94                      + System.getProperty("java.class.path"));
    95          }
    96      }
    97
    98      /***
    99       * Create a RuleSets from a list of names.
    100      *
    101      * @param ruleSetFileNames comma-separated list of rule set files.
    102      * @param class            a RuleSets object
    103      * @throws RuleSetNotFoundException
    104      */
    105     public RuleSets createRuleSets(String ruleSetFileNames, ClassLoader classLoader)
    106             throws RuleSetNotFoundException {
    107         RuleSets ruleSets = new RuleSets();
    108
    109         for (StringTokenizer st = new StringTokenizer(ruleSetFileNames, ","); st
    110                 .hasMoreTokens();) {
    111             RuleSet ruleSet = createSingleRuleSet(st.nextToken().trim(), classLoader);
    112             ruleSets.addRuleSet(ruleSet);
    113         }
    114
    115         return ruleSets;
    116     }
    117
    118     /***
    119      * Create a RuleSets from a list of names, using the classloader of this class.
    120      *
    121      * @param ruleSetFileNames comma-separated list of rule set files.
    122      * @throws RuleSetNotFoundException
    123      */
    124     public RuleSets createRuleSets(String ruleSetFileNames)
    125             throws RuleSetNotFoundException {
    126         return createRuleSets(ruleSetFileNames, getClass().getClassLoader());
    127     }
    128
    129     /***
    130      * Create a ruleset from a name or from a list of names
    131      *
    132      * @param name        name of rule set file loaded as a resource
    133      * @param classLoader the classloader used to load the ruleset and subsequent rules
    134      * @return the new ruleset
    135      * @throws RuleSetNotFoundException
    136      * @deprecated Use createRuleSets instead, because this method puts all rules in one
    137      *             single RuleSet object, and thus removes name and language of the
    138      *             originating rule set files.
    139      */
    140     public RuleSet createRuleSet(String name, ClassLoader classLoader)
    141             throws RuleSetNotFoundException {
    142         RuleSets ruleSets = createRuleSets(name, classLoader);
    143
    144         RuleSet result = new RuleSet();
    145         RuleSet[] allRuleSets = ruleSets.getAllRuleSets();
    146         for (int i = 0; i < allRuleSets.length; i++) {
    147             result.addRuleSet(allRuleSets[i]);
    148         }
    149
    150         return result;
    151     }
    152
    153     /***
    154      * Creates a ruleset. If passed a comma-delimited string
    155      * (rulesets/basic.xml,rulesets/unusedcode.xml) it will parse that string and create a
    156      * new ruleset for each item in the list. Same as createRuleSet(name,
    157      * ruleSetFactory.getClassLoader()).
    158      *
    159      * @param name name of rule set file loaded as a resource
    160      * @deprecated Use createRuleSets instead, because this method puts all rules in one
    161      *             single RuleSet object, and thus removes name and language of the
    162      *             originating rule set files.
    163      */
    164     public RuleSet createRuleSet(String name) throws RuleSetNotFoundException {
    165         return createRuleSet(name, getClass().getClassLoader());
    166     }
    167
    168     /***
    169      * Create a ruleset from a name
    170      *
    171      * @param name        name of rule set file loaded as a resource
    172      * @param classLoader the classloader used to load the ruleset and subsequent rules
    173      * @return the new ruleset
    174      * @throws RuleSetNotFoundException
    175      */
    176     public RuleSet createSingleRuleSet(String ruleSetFileName, ClassLoader classLoader)
    177             throws RuleSetNotFoundException {
    178         return createRuleSet(tryToGetStreamTo(ruleSetFileName, classLoader), classLoader);
    179     }
    180
    181     /***
    182      * Create a ruleset from a name
    183      *
    184      * @param name name of rule set file loaded as a resource
    185      * @return the new ruleset
    186      * @throws RuleSetNotFoundException
    187      */
    188     public RuleSet createSingleRuleSet(String ruleSetFileName)
    189             throws RuleSetNotFoundException {
    190         return createRuleSet(tryToGetStreamTo(ruleSetFileName, getClass()
    191                 .getClassLoader()));
    192     }
    193
    194     /***
    195      * Create a ruleset from an inputsteam. Same as createRuleSet(inputStream,
    196      * ruleSetFactory.getClassLoader()).
    197      *
    198      * @param inputStream an input stream that contains a ruleset descripion
    199      * @return a new ruleset
    200      */
    201     public RuleSet createRuleSet(InputStream inputStream) {
    202         return createRuleSet(inputStream, getClass().getClassLoader());
    203     }
    204
    205     /***
    206      * Create a ruleset from an input stream with a specified class loader
    207      *
    208      * @param inputStream an input stream that contains a ruleset descripion
    209      * @param classLoader a class loader used to load rule classes
    210      * @return a new ruleset
    211      */
    212     public RuleSet createRuleSet(InputStream inputStream, ClassLoader classLoader) {
    213         try {
    214             this.classLoader = classLoader;
    215             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    216             Document doc = builder.parse(inputStream);
    217             Element root = doc.getDocumentElement();
    218
    219             RuleSet ruleSet = new RuleSet();
    220             ruleSet.setName(root.getAttribute("name"));
    221             ruleSet.setLanguage(Language.getByName(root.getAttribute("language")));
    222
    223             NodeList nodeList = root.getChildNodes();
    224             for (int i = 0; i < nodeList.getLength(); i++) {
    225                 Node node = nodeList.item(i);
    226                 if (node.getNodeType() == Node.ELEMENT_NODE) {
    227                     if (node.getNodeName().equals("description")) {
    228                         ruleSet.setDescription(parseTextNode(node));
    229                     } else if (node.getNodeName().equals("rule")) {
    230                         parseRuleNode(ruleSet, node);
    231                     }
    232                 }
    233             }
    234
    235             return ruleSet;
    236         } catch (ClassNotFoundException cnfe) {
    237             cnfe.printStackTrace();
    238             throw new RuntimeException("Couldn't find that class " + cnfe.getMessage());
    239         } catch (InstantiationException ie) {
    240             ie.printStackTrace();
    241             throw new RuntimeException("Couldn't find that class " + ie.getMessage());
    242         } catch (IllegalAccessException iae) {
    243             iae.printStackTrace();
    244             throw new RuntimeException("Couldn't find that class " + iae.getMessage());
    245         } catch (ParserConfigurationException pce) {
    246             pce.printStackTrace();
    247             throw new RuntimeException("Couldn't find that class " + pce.getMessage());
    248         } catch (RuleSetNotFoundException rsnfe) {
    249             rsnfe.printStackTrace();
    250             throw new RuntimeException("Couldn't find that class " + rsnfe.getMessage());
    251         } catch (IOException ioe) {
    252             ioe.printStackTrace();
    253             throw new RuntimeException("Couldn't find that class " + ioe.getMessage());
    254         } catch (SAXException se) {
    255             se.printStackTrace();
    256             throw new RuntimeException("Couldn't find that class " + se.getMessage());
    257         }
    258     }
    259
    260     /***
    261      * Try to load a resource with the specified class loader
    262      *
    263      * @param name   a resource name (contains a ruleset description)
    264      * @param loader a class loader used to load that rule set description
    265      * @return an inputstream to that resource
    266      * @throws RuleSetNotFoundException
    267      */
    268     private InputStream tryToGetStreamTo(String name, ClassLoader loader)
    269             throws RuleSetNotFoundException {
    270         InputStream in = ResourceLoader.loadResourceAsStream(name, loader);
    271         if (in == null) {
    272             throw new RuleSetNotFoundException("Can't find resource "
    273                     + name
    274                     + ".  Make sure the resource is a valid file or URL or is on the CLASSPATH.  Here's the current classpath: "
    275                     + System.getProperty("java.class.path"));
    276         }
    277         return in;
    278     }
    279
    280     /***
    281      * Parse a rule node
    282      *
    283      * @param ruleSet  the ruleset being constructed
    284      * @param ruleNode must be a rule element node
    285      */
    286     private void parseRuleNode(RuleSet ruleSet, Node ruleNode)
    287             throws ClassNotFoundException, InstantiationException,
    288             IllegalAccessException, RuleSetNotFoundException {
    289         Element ruleElement = (Element) ruleNode;
    290         String ref = ruleElement.getAttribute("ref");
    291         if (ref.trim().length() == 0) {
    292             parseInternallyDefinedRuleNode(ruleSet, ruleNode);
    293         } else {
    294             parseExternallyDefinedRuleNode(ruleSet, ruleNode);
    295         }
    296     }
    297
    298     /***
    299      * Process a rule definition node
    300      *
    301      * @param ruleSet  the ruleset being constructed
    302      * @param ruleNode must be a rule element node
    303      */
    304     private void parseInternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode)
    305             throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    306         Element ruleElement = (Element) ruleNode;
    307
    308         String attribute = ruleElement.getAttribute("class");
    309         Rule rule = (Rule) classLoader.loadClass(attribute).newInstance();
    310
    311         rule.setName(ruleElement.getAttribute("name"));
    312         rule.setMessage(ruleElement.getAttribute("message"));
    313         rule.setRuleSetName(ruleSet.getName());
    314         rule.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
    315
    316         if (ruleElement.hasAttribute("dfa")
    317                 && ruleElement.getAttribute("dfa").equals("true")) {
    318             rule.setUsesDFA();
    319         }
    320
    321         for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
    322             Node node = ruleElement.getChildNodes().item(i);
    323             if (node.getNodeType() == Node.ELEMENT_NODE) {
    324                 if (node.getNodeName().equals("description")) {
    325                     rule.setDescription(parseTextNode(node));
    326                 } else if (node.getNodeName().equals("example")) {
    327                     rule.setExample(parseTextNode(node));
    328                 } else if (node.getNodeName().equals("priority")) {
    329                     rule.setPriority(new Integer(parseTextNode(node).trim()).intValue());
    330                 } else if (node.getNodeName().equals("properties")) {
    331                     Properties p = new Properties();
    332                     parsePropertiesNode(p, node);
    333                     for (Iterator j = p.keySet().iterator(); j.hasNext();) {
    334                         String key = (String) j.next();
    335                         rule.addProperty(key, p.getProperty(key));
    336                     }
    337                 }
    338             }
    339         }
    340         if (rule.getPriority() <= minPriority) {
    341             ruleSet.addRule(rule);
    342         }
    343     }
    344
    345     /***
    346      * Process a reference to a rule
    347      *
    348      * @param ruleSet  the ruleset being constructucted
    349      * @param ruleNode must be a rule element node
    350      */
    351     private void parseExternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode)
    352             throws RuleSetNotFoundException {
    353         Element ruleElement = (Element) ruleNode;
    354         String ref = ruleElement.getAttribute("ref");
    355         if (ref.endsWith("xml")) {
    356             parseRuleNodeWithExclude(ruleSet, ruleElement, ref);
    357         } else {
    358             parseRuleNodeWithSimpleReference(ruleSet, ruleNode, ref);
    359         }
    360     }
    361
    362     /***
    363      * Parse a rule node with a simple reference
    364      *
    365      * @param ruleSet the ruleset being constructed
    366      * @param ref     a reference to a rule
    367      */
    368     private void parseRuleNodeWithSimpleReference(RuleSet ruleSet, Node ruleNode,
    369                                                   String ref) throws RuleSetNotFoundException {
    370         RuleSetFactory rsf = new RuleSetFactory();
    371
    372         ExternalRuleID externalRuleID = new ExternalRuleID(ref);
    373         RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader
    374                 .loadResourceAsStream(externalRuleID.getFilename()));
    375         Rule externalRule = externalRuleSet.getRuleByName(externalRuleID.getRuleName());
    376         if (externalRule == null) {
    377             throw new IllegalArgumentException("Unable to find rule "
    378                     + externalRuleID.getRuleName()
    379                     + "; perhaps the rule name is mispelled?");
    380         }
    381
    382         OverrideParser p = new OverrideParser((Element) ruleNode);
    383         p.overrideAsNecessary(externalRule);
    384
    385         if (externalRule.getPriority() <= minPriority) {
    386             ruleSet.addRule(externalRule);
    387         }
    388     }
    389
    390     /***
    391      * Parse a reference rule node with excludes
    392      *
    393      * @param ruleSet     the ruleset being constructed
    394      * @param ruleElement must be a rule element
    395      * @param ref         the ruleset reference
    396      */
    397     private void parseRuleNodeWithExclude(RuleSet ruleSet, Element ruleElement, String ref)
    398             throws RuleSetNotFoundException {
    399         NodeList excludeNodes = ruleElement.getChildNodes();
    400         Set excludes = new HashSet();
    401         for (int i = 0; i < excludeNodes.getLength(); i++) {
    402             if ((excludeNodes.item(i).getNodeType() == Node.ELEMENT_NODE)
    403                     && (excludeNodes.item(i).getNodeName().equals("exclude"))) {
    404                 Element excludeElement = (Element) excludeNodes.item(i);
    405                 excludes.add(excludeElement.getAttribute("name"));
    406             }
    407         }
    408
    409         RuleSetFactory rsf = new RuleSetFactory();
    410         RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader
    411                 .loadResourceAsStream(ref));
    412         for (Iterator i = externalRuleSet.getRules().iterator(); i.hasNext();) {
    413             Rule rule = (Rule) i.next();
    414             if (!excludes.contains(rule.getName()) && rule.getPriority() <= minPriority) {
    415                 ruleSet.addRule(rule);
    416             }
    417         }
    418     }
    419
    420     private static String parseTextNode(Node exampleNode) {
    421         StringBuffer buffer = new StringBuffer();
    422         for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) {
    423             Node node = exampleNode.getChildNodes().item(i);
    424             if (node.getNodeType() == Node.CDATA_SECTION_NODE
    425                     || node.getNodeType() == Node.TEXT_NODE) {
    426                 buffer.append(node.getNodeValue());
    427             }
    428         }
    429         return buffer.toString();
    430     }
    431
    432     /***
    433      * Parse a properties node
    434      *
    435      * @param propertiesNode must be a properties element node
    436      */
    437     private static void parsePropertiesNode(Properties p, Node propertiesNode) {
    438         for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) {
    439             Node node = propertiesNode.getChildNodes().item(i);
    440             if (node.getNodeType() == Node.ELEMENT_NODE
    441                     && node.getNodeName().equals("property")) {
    442                 parsePropertyNode(p, node);
    443             }
    444         }
    445     }
    446
    447     /***
    448      * Parse a property node
    449      *
    450      * @param propertyNode must be a property element node
    451      */
    452     private static void parsePropertyNode(Properties p, Node propertyNode) {
    453         Element propertyElement = (Element) propertyNode;
    454         String name = propertyElement.getAttribute("name");
    455         String value = propertyElement.getAttribute("value");
    456         // TODO String desc = propertyElement.getAttribute("description");
    457         if (value.trim().length() == 0) {
    458             for (int i = 0; i < propertyNode.getChildNodes().getLength(); i++) {
    459                 Node node = propertyNode.getChildNodes().item(i);
    460                 if ((node.getNodeType() == Node.ELEMENT_NODE)
    461                         && node.getNodeName().equals("value")) {
    462                     value = parseTextNode(node);
    463                 }
    464             }
    465         }
    466         if (propertyElement.hasAttribute("pluginname")) {
    467             p.setProperty("pluginname", propertyElement.getAttributeNode("pluginname")
    468                     .getNodeValue());
    469         }
    470         p.setProperty(name, value);
    471     }
    472 }