1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.ArrayList;
23 import java.util.BitSet;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.regex.Pattern;
28
29 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
30 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31 import com.puppycrawl.tools.checkstyle.api.DetailAST;
32 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
34
35
36
37
38
39
40 @FileStatefulCheck
41 public class MultipleStringLiteralsCheck extends AbstractCheck {
42
43
44
45
46
47 public static final String MSG_KEY = "multiple.string.literal";
48
49
50
51
52 private final Map<String, List<DetailAST>> stringMap = new HashMap<>();
53
54
55
56
57 private final BitSet ignoreOccurrenceContext = new BitSet();
58
59
60
61
62
63 private int allowedDuplicates = 1;
64
65
66
67
68 private Pattern ignoreStringsRegexp;
69
70
71
72
73 public MultipleStringLiteralsCheck() {
74 setIgnoreStringsRegexp(Pattern.compile("^\"\"$"));
75 ignoreOccurrenceContext.set(TokenTypes.ANNOTATION);
76 }
77
78
79
80
81
82 public void setAllowedDuplicates(int allowedDuplicates) {
83 this.allowedDuplicates = allowedDuplicates;
84 }
85
86
87
88
89
90
91
92 public final void setIgnoreStringsRegexp(Pattern ignoreStringsRegexp) {
93 if (ignoreStringsRegexp == null || ignoreStringsRegexp.pattern().isEmpty()) {
94 this.ignoreStringsRegexp = null;
95 }
96 else {
97 this.ignoreStringsRegexp = ignoreStringsRegexp;
98 }
99 }
100
101
102
103
104
105 public final void setIgnoreOccurrenceContext(String... strRep) {
106 ignoreOccurrenceContext.clear();
107 for (final String s : strRep) {
108 final int type = TokenUtil.getTokenId(s);
109 ignoreOccurrenceContext.set(type);
110 }
111 }
112
113 @Override
114 public int[] getDefaultTokens() {
115 return getRequiredTokens();
116 }
117
118 @Override
119 public int[] getAcceptableTokens() {
120 return getRequiredTokens();
121 }
122
123 @Override
124 public int[] getRequiredTokens() {
125 return new int[] {TokenTypes.STRING_LITERAL};
126 }
127
128 @Override
129 public void visitToken(DetailAST ast) {
130 if (!isInIgnoreOccurrenceContext(ast)) {
131 final String currentString = ast.getText();
132 if (ignoreStringsRegexp == null || !ignoreStringsRegexp.matcher(currentString).find()) {
133 List<DetailAST> hitList = stringMap.get(currentString);
134 if (hitList == null) {
135 hitList = new ArrayList<>();
136 stringMap.put(currentString, hitList);
137 }
138 hitList.add(ast);
139 }
140 }
141 }
142
143
144
145
146
147
148
149
150
151 private boolean isInIgnoreOccurrenceContext(DetailAST ast) {
152 boolean isInIgnoreOccurrenceContext = false;
153 for (DetailAST token = ast;
154 token.getParent() != null;
155 token = token.getParent()) {
156 final int type = token.getType();
157 if (ignoreOccurrenceContext.get(type)) {
158 isInIgnoreOccurrenceContext = true;
159 break;
160 }
161 }
162 return isInIgnoreOccurrenceContext;
163 }
164
165 @Override
166 public void beginTree(DetailAST rootAST) {
167 stringMap.clear();
168 }
169
170 @Override
171 public void finishTree(DetailAST rootAST) {
172 for (Map.Entry<String, List<DetailAST>> stringListEntry : stringMap.entrySet()) {
173 final List<DetailAST> hits = stringListEntry.getValue();
174 if (hits.size() > allowedDuplicates) {
175 final DetailAST firstFinding = hits.get(0);
176 log(firstFinding, MSG_KEY, stringListEntry.getKey(), hits.size());
177 }
178 }
179 }
180
181 }