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