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.AbstractRule;
7   import net.sourceforge.pmd.RuleContext;
8   import net.sourceforge.pmd.ast.ASTArgumentList;
9   import net.sourceforge.pmd.ast.ASTCompilationUnit;
10  import net.sourceforge.pmd.ast.ASTLiteral;
11  import net.sourceforge.pmd.ast.ASTVariableInitializer;
12  import net.sourceforge.pmd.ast.Node;
13  import net.sourceforge.pmd.ast.SimpleNode;
14  
15  import java.io.BufferedReader;
16  import java.io.File;
17  import java.io.FileReader;
18  import java.io.IOException;
19  import java.io.LineNumberReader;
20  import java.text.MessageFormat;
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  public class AvoidDuplicateLiteralsRule extends AbstractRule {
30  
31      public static class ExceptionParser {
32  
33          private static final char ESCAPE_CHAR = '//';
34          private char delimiter;
35  
36          public ExceptionParser(char delimiter) {
37              this.delimiter = delimiter;
38          }
39  
40          public Set parse(String in) {
41              Set result = new HashSet();
42  
43              StringBuffer currentToken = new StringBuffer();
44              boolean inEscapeMode = false;
45  
46              for (int i=0; i<in.length(); i++) {
47  
48                  if (inEscapeMode) {
49                      inEscapeMode = false;
50                      currentToken.append(in.charAt(i));
51                      continue;
52                  }
53  
54                  if (!inEscapeMode && in.charAt(i) == ESCAPE_CHAR) {
55                      inEscapeMode = true;
56                      continue;
57                  }
58  
59                  if (in.charAt(i) == delimiter) {
60                      result.add(currentToken.toString());
61                      currentToken = new StringBuffer();
62                  } else {
63                      currentToken.append(in.charAt(i));
64                  }
65              }
66  
67              if (currentToken.length()>0) {
68                  result.add(currentToken.toString());
69                  currentToken = new StringBuffer();
70              }
71  
72              return result;
73          }
74      }
75  
76      private static final char DEFAULT_SEPARATOR = ',';
77      private static final String EXCEPTION_LIST_PROPERTY = "exceptionlist";
78      private static final String SEPARATOR_PROPERTY = "separator";
79      private static final String EXCEPTION_FILE_NAME_PROPERTY = "exceptionfile";
80  
81      private Map literals = new HashMap();
82      private Set exceptions = new HashSet();
83  
84      public Object visit(ASTCompilationUnit node, Object data) {
85          literals.clear();
86  
87          if (hasProperty(EXCEPTION_LIST_PROPERTY)) {
88              ExceptionParser p;
89              if (hasProperty(SEPARATOR_PROPERTY)) {
90                  p = new ExceptionParser(getStringProperty(SEPARATOR_PROPERTY).charAt(0));
91              } else {
92                  p = new ExceptionParser(DEFAULT_SEPARATOR);
93              }
94              exceptions = p.parse(getStringProperty(EXCEPTION_LIST_PROPERTY));
95          } else if (hasProperty(EXCEPTION_FILE_NAME_PROPERTY)) {
96              exceptions = new HashSet();
97              try {
98                  LineNumberReader reader = new LineNumberReader(new BufferedReader(new FileReader(new File(getStringProperty(EXCEPTION_FILE_NAME_PROPERTY)))));
99                  String line = null;
100                 while ((line = reader.readLine()) != null) {
101                     exceptions.add(line);
102                 }
103                 reader.close();
104             } catch (IOException ioe) {
105                 ioe.printStackTrace();
106             }
107         }
108 
109         super.visit(node, data);
110 
111         int threshold = getIntProperty("threshold");
112         for (Iterator i = literals.keySet().iterator(); i.hasNext();) {
113             String key = (String) i.next();
114             List occurrences = (List) literals.get(key);
115             if (occurrences.size() >= threshold) {
116                 Object[] args = new Object[]{new Integer(occurrences.size()), new Integer(((SimpleNode) occurrences.get(0)).getBeginLine())};
117                 String msg = MessageFormat.format(getMessage(), args);
118                 RuleContext ctx = (RuleContext) data;
119                 ctx.getReport().addRuleViolation(createRuleViolation(ctx, ((SimpleNode) occurrences.get(0)).getBeginLine(), msg));
120             }
121         }
122         return data;
123     }
124 
125     public Object visit(ASTLiteral node, Object data) {
126         if (!hasAtLeast4Parents(node) || (!fourthParentIsAnArgList(node) && !fourthParentIsAVariableInitializer(node))) {
127             return data;
128         }
129 
130         // just catching strings of 3 chars or more for now - no numbers
131         if (node.getImage() == null || node.getImage().indexOf('\"') == -1 || node.getImage().length() < 3) {
132             return data;
133         }
134 
135         // skip any exceptions
136         if (exceptions.contains(node.getImage().substring(1, node.getImage().length()-1))) {
137             return data;
138         }
139 
140         if (literals.containsKey(node.getImage())) {
141             List occurrences = (List) literals.get(node.getImage());
142             occurrences.add(node);
143         } else {
144             List occurrences = new ArrayList();
145             occurrences.add(node);
146             literals.put(node.getImage(), occurrences);
147         }
148 
149         return data;
150     }
151 
152     private boolean fourthParentIsAVariableInitializer(ASTLiteral node) {
153         return node.jjtGetParent().jjtGetParent().jjtGetParent().jjtGetParent() instanceof ASTVariableInitializer;
154     }
155 
156     private boolean fourthParentIsAnArgList(ASTLiteral node) {
157         return node.jjtGetParent().jjtGetParent().jjtGetParent().jjtGetParent() instanceof ASTArgumentList;
158     }
159 
160     private boolean hasAtLeast4Parents(Node node) {
161         Node currentNode = node;
162         for (int i = 0; i < 4; i++) {
163             if (currentNode instanceof ASTCompilationUnit) {
164                 return false;
165             }
166             currentNode = currentNode.jjtGetParent();
167         }
168         return true;
169     }
170 }
171