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
119 if (node.getImage() == null || node.getImage().indexOf('\"') == -1 || node.getImage().length() < 5) {
120 return data;
121 }
122
123
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