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