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
124 if (node.getImage() == null || node.getImage().indexOf('\"') == -1 || node.getImage().length() < 5) {
125 return data;
126 }
127
128
129 if (exceptions.contains(node.getImage().substring(1, node.getImage().length() - 1))) {
130 return data;
131 }
132
133
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