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