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.PropertyType;
31 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
32 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
33 import com.puppycrawl.tools.checkstyle.api.DetailAST;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
36 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 @FileStatefulCheck
87 public class MultipleStringLiteralsCheck extends AbstractCheck {
88
89
90
91
92
93 public static final String MSG_KEY = "multiple.string.literal";
94
95
96
97
98 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
99
100
101
102
103 private static final String QUOTE = "\"";
104
105
106
107
108 private final Map<String, List<DetailAST>> stringMap = new HashMap<>();
109
110
111
112
113
114
115 @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
116 private final BitSet ignoreOccurrenceContext = new BitSet();
117
118
119
120
121 private int allowedDuplicates = 1;
122
123
124
125
126 private Pattern ignoreStringsRegexp;
127
128
129
130
131 public MultipleStringLiteralsCheck() {
132 setIgnoreStringsRegexp(Pattern.compile("^\"\"$"));
133 ignoreOccurrenceContext.set(TokenTypes.ANNOTATION);
134 }
135
136
137
138
139
140
141
142 public void setAllowedDuplicates(int allowedDuplicates) {
143 this.allowedDuplicates = allowedDuplicates;
144 }
145
146
147
148
149
150
151
152
153
154
155 public final void setIgnoreStringsRegexp(Pattern ignoreStringsRegexp) {
156 if (ignoreStringsRegexp == null || ignoreStringsRegexp.pattern().isEmpty()) {
157 this.ignoreStringsRegexp = null;
158 }
159 else {
160 this.ignoreStringsRegexp = ignoreStringsRegexp;
161 }
162 }
163
164
165
166
167
168
169
170
171
172 public final void setIgnoreOccurrenceContext(String... strRep) {
173 ignoreOccurrenceContext.clear();
174 for (final String s : strRep) {
175 final int type = TokenUtil.getTokenId(s);
176 ignoreOccurrenceContext.set(type);
177 }
178 }
179
180 @Override
181 public int[] getDefaultTokens() {
182 return getRequiredTokens();
183 }
184
185 @Override
186 public int[] getAcceptableTokens() {
187 return getRequiredTokens();
188 }
189
190 @Override
191 public int[] getRequiredTokens() {
192 return new int[] {
193 TokenTypes.STRING_LITERAL,
194 TokenTypes.TEXT_BLOCK_CONTENT,
195 };
196 }
197
198 @Override
199 public void visitToken(DetailAST ast) {
200 if (!isInIgnoreOccurrenceContext(ast)) {
201 final String currentString;
202 if (ast.getType() == TokenTypes.TEXT_BLOCK_CONTENT) {
203 final String strippedString =
204 CheckUtil.stripIndentAndInitialNewLineFromTextBlock(ast.getText());
205
206 currentString = QUOTE + strippedString + QUOTE;
207 }
208 else {
209 currentString = ast.getText();
210 }
211 if (ignoreStringsRegexp == null
212 || !ignoreStringsRegexp.matcher(currentString).find()) {
213 stringMap.computeIfAbsent(currentString, key -> new ArrayList<>()).add(ast);
214 }
215 }
216 }
217
218
219
220
221
222
223
224
225
226 private boolean isInIgnoreOccurrenceContext(DetailAST ast) {
227 boolean isInIgnoreOccurrenceContext = false;
228 for (DetailAST token = ast; token != null; token = token.getParent()) {
229 final int type = token.getType();
230 if (ignoreOccurrenceContext.get(type)) {
231 isInIgnoreOccurrenceContext = true;
232 break;
233 }
234 }
235 return isInIgnoreOccurrenceContext;
236 }
237
238 @Override
239 public void beginTree(DetailAST rootAST) {
240 stringMap.clear();
241 }
242
243 @Override
244 public void finishTree(DetailAST rootAST) {
245 for (Map.Entry<String, List<DetailAST>> stringListEntry : stringMap.entrySet()) {
246 final List<DetailAST> hits = stringListEntry.getValue();
247 if (hits.size() > allowedDuplicates) {
248 final DetailAST firstFinding = hits.get(0);
249 final String recurringString =
250 ALL_NEW_LINES.matcher(
251 stringListEntry.getKey()).replaceAll("\\\\n");
252 log(firstFinding, MSG_KEY, recurringString, hits.size());
253 }
254 }
255 }
256 }
257