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