View Javadoc

1   /***
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules.strings;
5   
6   import net.sourceforge.pmd.AbstractRule;
7   import net.sourceforge.pmd.ast.ASTCompilationUnit;
8   import net.sourceforge.pmd.ast.ASTLiteral;
9   import net.sourceforge.pmd.ast.SimpleNode;
10  
11  import java.io.BufferedReader;
12  import java.io.File;
13  import java.io.FileReader;
14  import java.io.IOException;
15  import java.io.LineNumberReader;
16  import java.util.ArrayList;
17  import java.util.HashMap;
18  import java.util.HashSet;
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Set;
23  
24  public class AvoidDuplicateLiteralsRule extends AbstractRule {
25  
26      public static class ExceptionParser {
27  
28          private static final char ESCAPE_CHAR = '//';
29          private char delimiter;
30  
31          public ExceptionParser(char delimiter) {
32              this.delimiter = delimiter;
33          }
34  
35          public Set parse(String in) {
36              Set result = new HashSet();
37              StringBuffer currentToken = new StringBuffer();
38              boolean inEscapeMode = false;
39              for (int i = 0; i < in.length(); i++) {
40                  if (inEscapeMode) {
41                      inEscapeMode = false;
42                      currentToken.append(in.charAt(i));
43                      continue;
44                  }
45                  if (in.charAt(i) == ESCAPE_CHAR) {
46                      inEscapeMode = true;
47                      continue;
48                  }
49                  if (in.charAt(i) == delimiter) {
50                      result.add(currentToken.toString());
51                      currentToken = new StringBuffer();
52                  } else {
53                      currentToken.append(in.charAt(i));
54                  }
55              }
56              if (currentToken.length() > 0) {
57                  result.add(currentToken.toString());
58              }
59              return result;
60          }
61      }
62  
63      private static final char DEFAULT_SEPARATOR = ',';
64      private static final String EXCEPTION_LIST_PROPERTY = "exceptionlist";
65      private static final String SEPARATOR_PROPERTY = "separator";
66      private static final String EXCEPTION_FILE_NAME_PROPERTY = "exceptionfile";
67  
68      private Map literals = new HashMap();
69      private Set exceptions = new HashSet();
70  
71      public Object visit(ASTCompilationUnit node, Object data) {
72          literals.clear();
73  
74          if (hasProperty(EXCEPTION_LIST_PROPERTY)) {
75              ExceptionParser p;
76              if (hasProperty(SEPARATOR_PROPERTY)) {
77                  p = new ExceptionParser(getStringProperty(SEPARATOR_PROPERTY).charAt(0));
78              } else {
79                  p = new ExceptionParser(DEFAULT_SEPARATOR);
80              }
81              exceptions = p.parse(getStringProperty(EXCEPTION_LIST_PROPERTY));
82          } else if (hasProperty(EXCEPTION_FILE_NAME_PROPERTY)) {
83              exceptions = new HashSet();
84              LineNumberReader reader = null;
85              try {
86                  reader = new LineNumberReader(new BufferedReader(new FileReader(new File(getStringProperty(EXCEPTION_FILE_NAME_PROPERTY)))));
87                  String line;
88                  while ((line = reader.readLine()) != null) {
89                      exceptions.add(line);
90                  }
91              } catch (IOException ioe) {
92                  ioe.printStackTrace();
93              } finally {
94                  try {
95                      if (reader != null)
96                          reader.close();
97                  } catch (IOException ioe) {
98                      ioe.printStackTrace();
99                  }
100             }
101         }
102 
103         super.visit(node, data);
104 
105         int threshold = getIntProperty("threshold");
106         for (Iterator i = literals.keySet().iterator(); i.hasNext();) {
107             String key = (String) i.next();
108             List occurrences = (List) literals.get(key);
109             if (occurrences.size() >= threshold) {
110                 Object[] args = new Object[]{key, new Integer(occurrences.size()), new Integer(((SimpleNode) occurrences.get(0)).getBeginLine())};
111                 addViolation(data, (SimpleNode) occurrences.get(0), args);
112             }
113         }
114         return data;
115     }
116 
117     public Object visit(ASTLiteral node, Object data) {
118         // just catching strings of 5 chars or more (including the enclosing quotes) for now - no numbers
119         if (node.getImage() == null || node.getImage().indexOf('\"') == -1 || node.getImage().length() < 5) {
120             return data;
121         }
122 
123         // skip any exceptions
124         if (exceptions.contains(node.getImage().substring(1, node.getImage().length() - 1))) {
125             return data;
126         }
127 
128         if (literals.containsKey(node.getImage())) {
129             List occurrences = (List) literals.get(node.getImage());
130             occurrences.add(node);
131         } else {
132             List occurrences = new ArrayList();
133             occurrences.add(node);
134             literals.put(node.getImage(), occurrences);
135         }
136 
137         return data;
138     }
139 }
140