View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.logging;
5   
6   import java.util.ArrayList;
7   import java.util.Arrays;
8   import java.util.Collections;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Map.Entry;
13  
14  import net.sourceforge.pmd.Rule;
15  import net.sourceforge.pmd.lang.ast.Node;
16  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
17  import net.sourceforge.pmd.lang.java.rule.optimizations.AbstractOptimizationRule;
18  import net.sourceforge.pmd.lang.rule.properties.StringMultiProperty;
19  
20  import org.jaxen.JaxenException;
21  
22  /**
23   * Check that log.debug, log.trace, log.error, etc... statements are guarded by
24   * some test expression on log.isDebugEnabled() or log.isTraceEnabled().
25   * 
26   * @author Romain Pelisse - <belaran@gmail.com>
27   * @author Heiko Rupp - <hwr@pilhuhn.de>
28   * @author Tammo van Lessen - provided original XPath expression
29   * 
30   */
31  public class GuardLogStatementRule extends AbstractOptimizationRule implements Rule {
32  
33      public static final StringMultiProperty LOG_LEVELS = new StringMultiProperty("logLevels", "LogLevels to guard",
34              new String[] {}, 1.0f, ',');
35  
36      public static final StringMultiProperty GUARD_METHODS = new StringMultiProperty("guardsMethods",
37              "method use to guard the log statement", new String[] {}, 2.0f, ',');
38  
39      protected Map<String, String> guardStmtByLogLevel = new HashMap<>(5);
40  
41      private static final String XPATH_EXPRESSION = "//PrimaryPrefix[ends-with(Name/@Image, 'LOG_LEVEL')]"
42              + "[count(../descendant::AdditiveExpression) > 0]"
43              + "[count(ancestor::IfStatement/Expression/descendant::PrimaryExpression["
44              + "ends-with(descendant::PrimaryPrefix/Name/@Image,'GUARD')]) = 0]";
45  
46      public GuardLogStatementRule() {
47          definePropertyDescriptor(LOG_LEVELS);
48          definePropertyDescriptor(GUARD_METHODS);
49      }
50  
51      @Override
52      public Object visit(ASTCompilationUnit unit, Object data) {
53          extractProperties();
54          findViolationForEachLogStatement(unit, data, XPATH_EXPRESSION);
55          return super.visit(unit, data);
56      }
57  
58      protected void findViolationForEachLogStatement(ASTCompilationUnit unit, Object data, String xpathExpression) {
59          for (Entry<String, String> entry : guardStmtByLogLevel.entrySet()) {
60              List<? extends Node> nodes = findViolations(unit, entry.getKey(), entry.getValue(), xpathExpression);
61              for (Node node : nodes) {
62                  super.addViolation(data, node);
63              }
64          }
65      }
66  
67      @SuppressWarnings("unchecked")
68      private List<? extends Node> findViolations(ASTCompilationUnit unit, String logLevel, String guard, String xpathExpression) {
69          try {
70              return unit.findChildNodesWithXPath(xpathExpression
71                      .replaceAll("LOG_LEVEL_UPPERCASE", logLevel.toUpperCase()).replaceAll("LOG_LEVEL", logLevel)
72                      .replaceAll("GUARD", guard));
73          } catch (JaxenException e) {
74              e.printStackTrace();
75          }
76          return Collections.EMPTY_LIST;
77      }
78  
79      private void setPropertiesDefaultValues(List<String> logLevels, List<String> guardMethods) {
80          logLevels.add("trace");
81          logLevels.add("debug");
82          logLevels.add("info");
83          logLevels.add("warn");
84          logLevels.add("error");
85  
86          guardMethods.clear();
87          guardMethods.add("isTraceEnabled");
88          guardMethods.add("isDebugEnabled");
89          guardMethods.add("isInfoEnabled");
90          guardMethods.add("isWarnEnabled");
91          guardMethods.add("isErrorEnabled");
92      }
93  
94      protected void extractProperties() {
95          if (guardStmtByLogLevel.isEmpty()) {
96  
97              List<String> logLevels = new ArrayList<>(Arrays.asList(super.getProperty(LOG_LEVELS)));
98              List<String> guardMethods = new ArrayList<>(Arrays.asList(super.getProperty(GUARD_METHODS)));
99  
100             if (guardMethods.isEmpty() && !logLevels.isEmpty()) {
101                 throw new IllegalArgumentException("Can't specify guardMethods without specifiying logLevels.");
102             }
103 
104             if (logLevels.isEmpty()) {
105                 setPropertiesDefaultValues(logLevels, guardMethods);
106             }
107 
108             buildGuardStatementMap(logLevels, guardMethods);
109         }
110     }
111 
112     protected void buildGuardStatementMap(List<String> logLevels, List<String> guardMethods) {
113         for (String logLevel : logLevels) {
114             boolean found = false;
115             for (String guardMethod : guardMethods) {
116                 if (!found && guardMethod.toLowerCase().contains(logLevel.toLowerCase())) {
117                     found = true;
118                     guardStmtByLogLevel.put("." + logLevel, guardMethod);
119                 }
120             }
121 
122             if (!found) {
123                 throw new IllegalArgumentException("No guard method associated to the logLevel:" + logLevel
124                         + ". Should be something like 'is" + logLevel + "Enabled'.");
125             }
126         }
127     }
128 }