View Javadoc

1   /***
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules;
5   
6   import net.sourceforge.pmd.CommonAbstractRule;
7   import net.sourceforge.pmd.RuleContext;
8   import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
9   import net.sourceforge.pmd.ast.Node;
10  import net.sourceforge.pmd.ast.SimpleNode;
11  import net.sourceforge.pmd.jaxen.DocumentNavigator;
12  import net.sourceforge.pmd.jaxen.MatchesFunction;
13  
14  import org.jaxen.BaseXPath;
15  import org.jaxen.JaxenException;
16  import org.jaxen.SimpleVariableContext;
17  import org.jaxen.XPath;
18  
19  import java.io.PrintStream;
20  import java.io.PrintWriter;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map.Entry;
24  
25  /***
26   * Rule that tries to match an XPath expression against a DOM
27   * view of the AST of a "compilation unit".
28   * <p/>
29   * This rule needs a property "xpath".
30   */
31  public class XPathRule extends CommonAbstractRule {
32  
33      public static Class loadClass(ClassLoader classloader, String xpath, String name) {
34          if (xpath.indexOf('|') != -1) {
35              //System.err.println(name + " not a dynamic rule: " + xpath.trim().replaceAll("\n", ""));
36              return XPathRule.class;
37          }
38          String part = xpath.trim();
39          
40          // Need to use DOTALL mode because of potential line terminators
41          if (!part.matches("(?s)////w+//W.*")) {
42              //System.err.println(name + " not a dynamic rule: " + xpath.trim().replaceAll("\n", ""));
43              return XPathRule.class;           
44          }
45          
46          String tail = part.replaceFirst("^////w+", "");
47          String nodeName = part.substring(2, part.indexOf(tail));
48  
49          return DynamicXPathRule.loadClass(classloader, nodeName);
50      }
51  
52      private XPath xpath;
53      private boolean regexpFunctionRegistered;
54  
55      /***
56       * Evaluate the AST with compilationUnit as root-node, against
57       * the XPath expression found as property with name "xpath".
58       * All matches are reported as violations.
59       *
60       * @param compilationUnit the Node that is the root of the AST to be checked
61       * @param data
62       */
63      public void evaluate(Node compilationUnit, RuleContext data) {
64          try {
65              initializeXPathExpression();
66              List results = xpath.selectNodes(compilationUnit);
67              for (Iterator i = results.iterator(); i.hasNext();) {
68                  SimpleNode n = (SimpleNode) i.next();
69                  if (n instanceof ASTVariableDeclaratorId && getBooleanProperty("pluginname")) {
70                      addViolation(data, n, n.getImage());
71                  } else {
72                      addViolation(data, (SimpleNode) n, getMessage());
73                  }
74              }
75          } catch (JaxenException ex) {
76              throwJaxenAsRuntime(ex);
77          }
78      }
79  
80      private void initializeXPathExpression() throws JaxenException {
81          if (xpath != null) {
82              return;
83          }
84  
85          if (!regexpFunctionRegistered) {
86              MatchesFunction.registerSelfInSimpleContext();
87              regexpFunctionRegistered = true;
88          }
89  
90          xpath = new BaseXPath(getStringProperty("xpath"), new DocumentNavigator());
91          if (properties.size() > 1) {
92              SimpleVariableContext vc = new SimpleVariableContext();
93              for (Iterator i = properties.entrySet().iterator(); i.hasNext();) {
94                  Entry e = (Entry) i.next();
95                  if (!"xpath".equals(e.getKey())) {
96                      vc.setVariableValue((String) e.getKey(), e.getValue());
97                  }
98              }
99              xpath.setVariableContext(vc);
100         }
101     }
102 
103     private static void throwJaxenAsRuntime(final JaxenException ex) {
104         throw new RuntimeException() {
105             public void printStackTrace() {
106                 super.printStackTrace();
107                 ex.printStackTrace();
108             }
109 
110             public void printStackTrace(PrintWriter writer) {
111                 super.printStackTrace(writer);
112                 ex.printStackTrace(writer);
113             }
114 
115             public void printStackTrace(PrintStream stream) {
116                 super.printStackTrace(stream);
117                 ex.printStackTrace(stream);
118             }
119 
120             public String getMessage() {
121                 return super.getMessage() + ex.getMessage();
122             }
123         };
124     }
125 
126     /***
127      * Apply the rule to all compilation units.
128      */
129     public void apply(List astCompilationUnits, RuleContext ctx) {
130         for (Iterator i = astCompilationUnits.iterator(); i.hasNext();) {
131             evaluate((Node) i.next(), ctx);
132         }
133     }
134 }